diff --git a/README.md b/README.md index e625e91..cd7d020 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,16 @@ # devx — Reusable Development & CI/CD Tools -A Python package providing reusable development and CI/CD automation tools for oblachno-oss projects. devx consolidates release management, PR automation, wiki sync, badge generation, translation checks, and more into a single installable package. +A Python package providing reusable development and CI/CD automation tools for +oblachno-oss projects. devx consolidates release management, PR automation, +wiki sync, badge generation, translation checks, documentation coverage, +parallel test distribution, and more into a single installable package. + +It was extracted from the [GRM](https://git.oblachno.oblachno.fyi/oblachno-oss/grm) +project to be reusable across all oblachno-oss repositories. Any project hosted +on a Gitea instance with Gitea Actions can install devx and inherit a complete, +opinionated CI/CD pipeline: conventional commits, automated versioning via +git-cliff, squash-merge automation, Vikunja task tracking, wiki sync, and +quality badges. > An open-source project from **Oblachno** (облачно means *cloudy* in Bulgarian). @@ -13,6 +23,40 @@ A Python package providing reusable development and CI/CD automation tools for o [![Version](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/raw/commit/f54c01f92f5111d63afc782a44dada6569da9a6b/version.svg)](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/releases) [![Python](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/raw/commit/f54c01f92f5111d63afc782a44dada6569da9a6b/python.svg)](https://www.python.org/downloads/) +## Why devx? + +Every oblachno-oss project shares the same CI/CD needs: automated releases, +PR review, task tracking, documentation sync, and quality badges. Without a +shared package, each repository duplicates this logic in shell scripts and +workflow YAML, leading to drift, bugs, and maintenance burden. + +devx solves this by providing a single, tested Python package that any +oblachno-oss project can install. The project declares its configuration via +environment variables and `pyproject.toml`, and devx handles the rest. Updates +to the CI/CD pipeline ship as new devx releases — consumer projects pick them +up by bumping their devx dependency. + +### Key features + +- **Automated releases** — git-cliff-driven semver versioning, changelog + generation, tagging, and publishing to a Gitea PyPI registry. +- **PR automation** — squash-merge with task ID validation, automated PR + review with inline comments, and conventional commit enforcement. +- **Smart change classification** — user-facing vs workflow-only change + detection so infrastructure-only changes skip releases. +- **Documentation sync** — push `docs/` markdown to the Gitea wiki with + integrity verification. +- **Quality badges** — generate self-contained SVG badges for coverage, + tests, docs, quality, version, and Python version. +- **Translation checks** — validate i18n keys against source code, detect + dead keys and missing languages. +- **Parallel test distribution** — split test files or molecule scenarios + across CI runners with cross-runner fail-fast. +- **Developer tools** — environment setup, CI tool installation, test speed + enforcement, repository configuration. +- **i18n** — built-in translations for English, Bulgarian, German, Russian, + and Chinese; projects can extend with their own keys. + ## Installation Install from the Gitea PyPI registry: @@ -27,21 +71,38 @@ Or add the registry to your `pip.conf` / `pyproject.toml` and install normally: pip install devx ``` -## Usage +### Optional extras -### CI/CD Automation +devx ships optional dependency groups for different use cases: -devx provides CI/CD modules invoked via `python -m devx.ci.*`: +```bash +pip install "devx[ci,lint]" # CI runners and linting (pytest, ruff, pyright, bandit) +pip install "devx[molecule]" # Molecule testing for Ansible projects +pip install "devx[dev]" # Full local development (ci + lint + build + twine) +``` + +## Quick start + +After installing devx, set the required environment variables (see +[Configuration](#configuration)) and invoke modules via `python -m devx.*` or +the `devx` CLI. + +### CI/CD automation + +CI/CD modules are invoked via `python -m devx.ci.*`. Each module is also +available as a `devx ci ` subcommand. ```bash # Release automation (versioning, changelog, tagging) python -m devx.ci.release -python -m devx.ci.release --dry-run +python -m devx.ci.release --dry-run # preview without changes +python -m devx.ci.release --verify # check tag/version/changelog alignment # Publish a release to the Gitea PyPI registry python -m devx.ci.publish v1.0.0 oblachno-oss/devx +python -m devx.ci.publish v1.0.0 oblachno-oss/devx --skip-build # Gitea release only -# Automated PR review +# Automated PR review (posts inline comments and structured review) python -m devx.ci.pr_review 42 oblachno-oss/devx # Auto-merge a PR (validates title, squash-merges) @@ -55,9 +116,11 @@ python -m devx.ci.sync_wiki --repo oblachno-oss/devx --strict # Generate and push quality badges python -m devx.ci.push_badges +python -m devx.ci.push_badges --retries 3 # retry on git push failures # Check translation completeness python -m devx.ci.check_translations +python -m devx.ci.check_translations --translations path/to/translations.json # Documentation coverage check python -m devx.ci.doc_coverage --fail-on-missing @@ -65,56 +128,215 @@ python -m devx.ci.doc_coverage --fail-on-missing # Validate a commit message python -m devx.ci.validate_commit_msg commit-msg.txt --branch master +# Detect whether the latest commit is a release commit +python -m devx.ci.detect_release_commit + # Notify on CI failure (creates a Gitea issue) -python -m devx.ci.notify_failure --repo oblachno-oss/devx --run-id 123 --workflow ci --commit abc123 +python -m devx.ci.notify_failure --repo oblachno-oss/devx --run-id 123 \ + --workflow ci --commit abc123 --auto-login + +# Discover available Gitea Actions runners +python -m devx.ci.discover_runners --owner oblachno-oss --repo devx --indices + +# Distribute files across parallel runners (round-robin) +python -m devx.ci.distribute_files --pattern "tests/integration/test_*.py" \ + --runner-index 1 --max-runners 3 --github-env + +# Merge JUnit XML reports from parallel runners +python -m devx.ci.merge_junit --pattern "junit-results/runner-*.xml" --output junit-merged.xml + +# Run pytest with cross-runner fail-fast and JUnit output +python -m devx.ci.integration_guard --junit-output junit-results/runner-1.xml -- test_a.py test_b.py ``` -### Developer Tools +### Developer tools -devx provides developer tooling invoked via `python -m devx.tools.*`: +Developer tooling modules are invoked via `python -m devx.tools.*` or the +`devx tools ` subcommand. ```bash -# Set up a development environment (venv, deps, hooks) +# Set up a development environment (venv, deps, hooks, tea login) python -m devx.tools.setup --bin .venv/bin +python -m devx.tools.setup --bin .venv/bin --extras "ci,lint" --no-pre-commit # Install CI tools (actionlint, git-cliff, act_runner, tea) python -m devx.tools.install_tools python -m devx.tools.install_tools --tool git-cliff --tool tea +python -m devx.tools.install_tools --list + +# Install checkmake (Makefile linter) +python -m devx.tools.install_checkmake # Check unit test speed python -m devx.tools.check_test_speed --max-seconds 10 +python -m devx.tools.check_test_speed --max-seconds 4 --max-single-seconds 0.5 # Configure repository (branch protection, labels) -python -m devx.tools.configure_repo +python -m devx.tools.configure_repo --repo devx --owner oblachno-oss + +# Generate badge SVG files locally +python -m devx.tools.generate_badges --output-dir .badges/ + +# Generate a cliff.toml with the correct task ID prefix +python -m devx.tools.generate_cliff_config --prefix GRM +python -m devx.tools.generate_cliff_config --prefix GRM --force # overwrite existing ``` -### CLI +### Molecule testing (optional) -devx also provides a `devx` CLI command: +For projects with Ansible roles, devx provides molecule testing helpers via +`python -m devx.molecule.*` or `devx molecule `. + +```bash +# Distribute molecule scenarios across parallel runners +python -m devx.molecule.distribute_molecule --runner-index 1 --max-runners 3 +python -m devx.molecule.distribute_molecule --list # list all scenarios +python -m devx.molecule.distribute_molecule --list-platforms # list platforms + +# Run molecule tests with cross-runner fail-fast +python -m devx.molecule.molecule_ci_guard --junit-output junit.xml pair1 pair2 +python -m devx.molecule.molecule_ci_guard --roles-root ansible/roles pair1 pair2 + +# Run all molecule scenarios locally (sequential) +python -m devx.molecule.molecule_all +python -m devx.molecule.molecule_all --bin .venv/bin + +# Discover available Gitea Actions runners for molecule tests +python -m devx.molecule.discover_runners --indices + +# Ensure Docker is available for molecule tests in CI +python -m devx.molecule.start_docker +``` + +### OpenTofu helpers + +devx provides reusable functions for extracting values from `tofu output`: + +```python +from devx.opentofu import get_tofu_output, get_tofu_vm_ip, get_tofu_vm_field + +vms = get_tofu_output("customer_vms", cwd="tofu/environments/staging", + env={"HCLOUD_TOKEN": token}) +ip = get_tofu_vm_ip("customer_vms", "oblachno", cwd="tofu/environments/staging", + env={"HCLOUD_TOKEN": token}) +``` + +## CLI commands overview + +devx provides a `devx` CLI command with three command groups: ```bash devx --help devx --version ``` -### Configuration +### `devx ci` — CI/CD automation -devx reads configuration from environment variables with `.env` file fallback: +| Command | Description | +|---------|-------------| +| `devx ci auto-merge` | Squash-merge a PR with task ID validation | +| `devx ci check-translations` | Check translation files for gaps and dead keys | +| `devx ci classify-changes` | Classify git changes as user-facing or workflow-only | +| `devx ci detect-release-commit` | Detect whether the latest commit is a release commit | +| `devx ci discover-runners` | Discover available Gitea Actions runners | +| `devx ci distribute-files` | Distribute files across parallel runners (round-robin) | +| `devx ci doc-coverage` | Check documentation coverage for CLI commands and modules | +| `devx ci integration-guard` | Run pytest with cross-runner fail-fast and JUnit output | +| `devx ci merge-junit` | Merge JUnit XML reports from parallel runners | +| `devx ci notify-failure` | Create a Gitea issue when a CI workflow fails | +| `devx ci post-merge` | Update Vikunja task after a merge to master | +| `devx ci pr-review` | Run automated PR review | +| `devx ci publish` | Build package, publish to registry, create Gitea release | +| `devx ci push-badges` | Generate badge SVG files and push to the badges branch | +| `devx ci release` | Automated release: version, changelog, tag, push | +| `devx ci sync-wiki` | Sync documentation from docs/ to the Gitea wiki | +| `devx ci validate-commit-msg` | Validate commit messages for conventional format | + +### `devx tools` — Developer tools + +| Command | Description | +|---------|-------------| +| `devx tools check-test-speed` | Run unit tests and enforce execution-time budgets | +| `devx tools configure-repo` | Configure branch protection and labels via Gitea API | +| `devx tools generate-badges` | Generate self-contained SVG badge files | +| `devx tools generate-cliff-config` | Generate a cliff.toml with the correct task ID prefix | +| `devx tools install-checkmake` | Install checkmake (Makefile linter) | +| `devx tools install-tools` | Install actionlint, git-cliff, act_runner, tea | +| `devx tools setup` | Project setup: install deps, hooks, tea login | + +### `devx molecule` — Molecule testing (optional) + +| Command | Description | +|---------|-------------| +| `devx molecule all` | Run all molecule scenarios on all supported platforms | +| `devx molecule discover-runners` | Discover available Gitea Actions runners | +| `devx molecule distribute` | Distribute molecule test pairs across parallel runners | +| `devx molecule guard` | Run molecule tests with CI failure polling | + +See [CLI Commands](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/wiki/CLI-Commands) +in the wiki for full command documentation with examples. + +## Configuration + +devx reads configuration from environment variables with `.env` file fallback. +The config system loads `.env` automatically via `python-dotenv`. + +### DEVX_ environment variables | Variable | Default | Description | |----------|---------|-------------| | `DEVX_GITEA_API_URL` | `https://git.oblachno.oblachno.fyi/api/v1` | Gitea API base URL | | `DEVX_VIKUNJA_API_URL` | `https://work.oblachno.oblachno.fyi/api/v1` | Vikunja API base URL | -| `DEVX_LANG` | `en` | Language (en, bg) | +| `DEVX_REPO_OWNER` | **(none — must be set)** | Repository owner for API calls | +| `DEVX_REPO_NAME` | **(none — must be set)** | Repository name (or `owner/repo`) | +| `DEVX_TASK_PREFIX` | `DEVX` | Task ID prefix (GRM, OBL-INFRA, etc.) | +| `DEVX_VIKUNJA_PROJECT_ID` | `6` | Vikunja project ID | +| `DEVX_LANG` | `en` | Language for i18n (en, bg, de, ru, zh) | +| `DEVX_TRANSLATIONS_PATH` | — | Path to a custom JSON translations file | +| `DEVX_VERSION_FILE` | `src/devx/__init__.py` | Version source file (used by release) | +| `DEVX_DOCS_DIR` | `docs` | Documentation directory (used by sync_wiki) | +| `DEVX_STATUS_CHECKS` | `CI / quality (pull_request)` | Comma-separated status check contexts | +| `DEVX_PYPI_REGISTRY_URL` | — | Gitea PyPI registry URL (used by publish) | | `REPO_TOKEN` | — | Gitea API token | | `VIKUNJA_TOKEN` | — | Vikunja API token | +| `PYPI_TOKEN` | — | Standard PyPI token (takes precedence over Gitea registry) | -Copy `.env.example` to `.env` and fill in your tokens: +### Per-project overrides + +Projects using devx can override the default API URLs and language by setting +`DEVX_*` environment variables or entries in their `.env` file. Copy +`.env.example` to `.env` and fill in your tokens: ```bash cp .env.example .env ``` +### Change classification + +Projects configure which file paths are infrastructure (no release needed) vs +user-facing (release needed) in `pyproject.toml`: + +```toml +[tool.devx.classify] +# Merge with DEFAULT_INFRASTRUCTURE (CI workflows, tests, docs, config) +# use_defaults = true # (default) + +# Project-specific infrastructure paths (merged with defaults) +infrastructure = [] + +# Files that would default to user-facing but are actually infrastructure +infrastructure_overrides = [ + "src/myproject/__init__.py", # only contains __version__ +] + +# Safety override for broad infrastructure patterns +user_facing_overrides = [] + +# Tag patterns for CI conditional execution (orthogonal to release impact) +[tool.devx.classify.tags] +# ansible = ["ansible/**"] +``` + ## Development ```bash @@ -123,10 +345,86 @@ cd devx make setup # Create venv, install deps, hooks, CI tools make lint-all # ruff + pyright + bandit + actionlint make pytest-cov # Unit tests with 100% coverage +make test-unit # Unit tests without coverage +make workflow-check # Static + dry-run validation of workflow YAML +make clean # Remove caches, build artifacts, coverage data ``` -See [AGENTS.md](AGENTS.md) for full project conventions, PR workflow, and architecture details. +`make setup` automatically installs all development tools: +- **Python deps** via `python -m devx.tools.setup` (pip install -e .[dev], pre-commit hooks) +- **actionlint, git-cliff, act_runner, tea** via `python -m devx.tools.install_tools` +- **tea CLI login** via `python -m devx.tools.setup` (configures `tea login` from `.env`) + +### Make targets + +| Target | Description | +|--------|-------------| +| `make setup` | Full local development setup (venv, deps, hooks, CI tools) | +| `make setup-ci` | Lean setup for CI jobs (pytest + lint + runtime deps) | +| `make setup-quality` | Setup for quality job (lint + test deps, actionlint) | +| `make setup-release` | Setup for release jobs (git-cliff, tea, lint tools) | +| `make install-tools` | Install actionlint, git-cliff, act_runner, tea | +| `make install-hooks` | Install git hooks (pre-commit, pre-push) | +| `make lint` | ruff check + ruff format check + pyright + bandit | +| `make lint-ruff` | ruff check only | +| `make lint-format` | ruff format check only | +| `make typecheck` | pyright only | +| `make lint-bandit` | bandit security scan only | +| `make lint-all` | lint + workflow-lint (actionlint) | +| `make lint-deps` | pip-audit dependency vulnerability scan | +| `make test-unit` | Unit tests without coverage | +| `make pytest-cov` | Unit tests with 100% coverage enforcement | +| `make workflow-lint` | actionlint on .gitea/workflows/*.yml | +| `make workflow-dryrun` | act_runner exec --dryrun on all workflows | +| `make workflow-check` | workflow-lint + workflow-dryrun | +| `make clean` | Remove caches, build artifacts, coverage data | + +See [AGENTS.md](AGENTS.md) for full project conventions, PR workflow, and +architecture details. + +## Architecture overview + +devx is a self-contained Python package under `src/devx/`. It never imports +from scripts outside the package. All tools are invoked via +`python -m devx.ci.*`, `python -m devx.tools.*`, or `python -m devx.molecule.*`. + +``` +src/devx/ +├── __init__.py # Version (single source of truth, read by setuptools) +├── cli.py # Click-based CLI entry point (devx command) +├── config.py # Configuration system (DEVX_ env vars, .env loading) +├── api_clients.py # GiteaClient, VikunjaClient — HTTP API wrappers +├── gitea_cli.py # TeaCLI — wrapper around tea CLI with JSON parsing +├── i18n.py # Translation system (gettext-based, translations.json) +├── exceptions.py # Custom exception types (DevxError, APIError) +├── opentofu.py # OpenTofu output helpers +├── translations.json # Translation strings (en, bg, de, ru, zh) +├── ci/ # CI/CD automation modules (run by workflows) +├── tools/ # Developer tooling modules (run locally or by CI) +└── molecule/ # Optional molecule testing helpers (for Ansible projects) +``` + +### Design principles + +- **Self-contained package** — `src/devx/` never imports from scripts outside the package +- **Module-based invocation** — All tools invoked via `python -m devx.ci.*` or `python -m devx.tools.*` +- **PYTHONPATH: src** — Workflows set `PYTHONPATH: src` (not `.:src` since there are no scripts at repo root) +- **Config via env vars** — `DEVX_*` environment variables with `.env` file fallback +- **100% test coverage** — enforced by `--cov-fail-under=100` +- **i18n by default** — all user-facing strings wrapped in `_()` for translation + +See [Architecture](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/wiki/Architecture) +and [CI/CD Workflow](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/wiki/CI-CD-Workflow) +in the wiki for detailed documentation. + +## Links + +- **Wiki**: [https://git.oblachno.oblachno.fyi/oblachno-oss/devx/wiki](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/wiki) +- **Releases**: [https://git.oblachno.oblachno.fyi/oblachno-oss/devx/releases](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/releases) +- **Actions**: [https://git.oblachno.oblachno.fyi/oblachno-oss/devx/actions](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/actions) +- **Source**: [https://git.oblachno.oblachno.fyi/oblachno-oss/devx](https://git.oblachno.oblachno.fyi/oblachno-oss/devx) +- **GRM (origin project)**: [https://git.oblachno.oblachno.fyi/oblachno-oss/grm](https://git.oblachno.oblachno.fyi/oblachno-oss/grm) ## License -GPL-3.0 +GPL-3.0 — see [LICENSE](LICENSE). diff --git a/docs/index.md b/docs/index.md index ff73f06..f9a14a2 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,6 +1,12 @@ # devx — Reusable Development & CI/CD Tools -A Python package providing reusable development and CI/CD automation tools for oblachno-oss projects. +A Python package providing reusable development and CI/CD automation tools for +oblachno-oss projects. devx consolidates release management, PR automation, +wiki sync, badge generation, translation checks, documentation coverage, +parallel test distribution, and more into a single installable package. + +It was extracted from the [GRM](https://git.oblachno.oblachno.fyi/oblachno-oss/grm) +project to be reusable across all oblachno-oss repositories. > An open-source project from **Oblachno** (облачно means *cloudy* in Bulgarian). @@ -15,7 +21,28 @@ A Python package providing reusable development and CI/CD automation tools for o ## Overview -devx consolidates release management, PR automation, wiki sync, badge generation, translation checks, and more into a single installable package. It was extracted from the [GRM](https://git.oblachno.oblachno.fyi/oblachno-oss/grm) project to be reusable across all oblachno-oss projects. +devx provides a complete, opinionated CI/CD pipeline for any project hosted on +a Gitea instance with Gitea Actions. Install the package, declare configuration +via environment variables and `pyproject.toml`, and inherit: + +- **Automated releases** — git-cliff-driven semver versioning, changelog + generation, tagging, and publishing to a Gitea PyPI registry. +- **PR automation** — squash-merge with task ID validation, automated PR + review with inline comments, and conventional commit enforcement. +- **Smart change classification** — user-facing vs workflow-only change + detection so infrastructure-only changes skip releases. +- **Documentation sync** — push `docs/` markdown to the Gitea wiki with + integrity verification. +- **Quality badges** — self-contained SVG badges for coverage, tests, docs, + quality, version, and Python version. +- **Translation checks** — validate i18n keys against source code, detect + dead keys and missing languages. +- **Parallel test distribution** — split test files or molecule scenarios + across CI runners with cross-runner fail-fast. +- **Developer tools** — environment setup, CI tool installation, test speed + enforcement, repository configuration. +- **i18n** — built-in translations for English, Bulgarian, German, Russian, + and Chinese; projects can extend with their own keys. ## Installation @@ -25,11 +52,92 @@ Install from the Gitea PyPI registry: pip install devx --index-url https://git.oblachno.oblachno.fyi/api/packages/oblachno-oss/pypi/simple ``` +Optional extras: + +```bash +pip install "devx[ci,lint]" # CI runners and linting +pip install "devx[molecule]" # Molecule testing for Ansible projects +pip install "devx[dev]" # Full local development +``` + ## Architecture -- **Core modules** — config, exceptions, i18n, api_clients, gitea_cli -- **CI automation** (`devx.ci`) — release, publish, auto_merge, pr_review, classify_changes, etc. -- **Dev tools** (`devx.tools`) — setup, install_tools, check_test_speed, configure_repo, generate_badges -- **Molecule tools** (`devx.molecule`) — Optional, for projects with Ansible roles +devx is a self-contained Python package under `src/devx/`: -See [AGENTS.md](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/src/branch/master/AGENTS.md) for full project conventions. +- **Core modules** — `config.py`, `exceptions.py`, `i18n.py`, `api_clients.py`, + `gitea_cli.py`, `cli.py`, `opentofu.py` +- **CI automation** (`devx.ci`) — release, publish, auto_merge, pr_review, + classify_changes, sync_wiki, push_badges, check_translations, doc_coverage, + validate_commit_msg, detect_release_commit, notify_failure, post_merge, + discover_runners, distribute_files, merge_junit, integration_guard +- **Dev tools** (`devx.tools`) — setup, install_tools, check_test_speed, + configure_repo, generate_badges, generate_cliff_config, install_checkmake +- **Molecule tools** (`devx.molecule`) — Optional, for projects with Ansible + roles: distribute_molecule, molecule_ci_guard, molecule_all, discover_runners, + start_docker, platforms + +See [Architecture](Architecture) for the full package structure, module +descriptions, design principles, and data flow diagrams. + +## CI/CD pipeline + +devx uses Gitea Actions with three workflows: + +- **CI** (`ci.yml`) — runs on pull requests: quality checks, change detection, + release dry-run, automated PR review, and auto-merge. +- **Post-merge** (`post-merge.yml`) — runs on every push to master: release + versioning, wiki sync, badge generation, Vikunja task updates, and repo + configuration. +- **Publish** (`publish.yml`) — runs on tag pushes: builds the package, + publishes to the Gitea PyPI registry, and creates a Gitea release. + +See [CI/CD Workflow](CI-CD-Workflow) for the full pipeline documentation, +including the post-merge job graph, release process, badge generation, and +wiki sync details. + +## CLI commands + +devx provides a `devx` CLI with three command groups: + +- `devx ci ` — CI/CD automation (17 commands) +- `devx tools ` — Developer tools (7 commands) +- `devx molecule ` — Molecule testing (4 commands, optional) + +See [CLI Commands](CLI-Commands) for full command documentation with examples. + +## Configuration + +devx reads configuration from `DEVX_*` environment variables with `.env` file +fallback. Key variables: + +| Variable | Default | Description | +|----------|---------|-------------| +| `DEVX_GITEA_API_URL` | `https://git.oblachno.oblachno.fyi/api/v1` | Gitea API base URL | +| `DEVX_VIKUNJA_API_URL` | `https://work.oblachno.oblachno.fyi/api/v1` | Vikunja API base URL | +| `DEVX_REPO_OWNER` | **(must be set)** | Repository owner | +| `DEVX_REPO_NAME` | **(must be set)** | Repository name | +| `DEVX_TASK_PREFIX` | `DEVX` | Task ID prefix (GRM, OBL-INFRA, etc.) | +| `DEVX_LANG` | `en` | Language for i18n (en, bg, de, ru, zh) | +| `REPO_TOKEN` | — | Gitea API token | +| `VIKUNJA_TOKEN` | — | Vikunja API token | + +See [AGENTS.md](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/src/branch/master/AGENTS.md) +for the full configuration reference, PR workflow, and project conventions. + +## Wiki pages + +- [Home](Home) — This page +- [CLI Commands](CLI-Commands) — Full CLI command documentation with examples +- [Architecture](Architecture) — Package structure, module descriptions, design principles +- [CI/CD Workflow](CI-CD-Workflow) — Pipeline documentation, workflows, and CI scripts + +## Links + +- **Source**: [https://git.oblachno.oblachno.fyi/oblachno-oss/devx](https://git.oblachno.oblachno.fyi/oblachno-oss/devx) +- **Releases**: [https://git.oblachno.oblachno.fyi/oblachno-oss/devx/releases](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/releases) +- **Actions**: [https://git.oblachno.oblachno.fyi/oblachno-oss/devx/actions](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/actions) +- **GRM (origin project)**: [https://git.oblachno.oblachno.fyi/oblachno-oss/grm](https://git.oblachno.oblachno.fyi/oblachno-oss/grm) + +## License + +GPL-3.0 diff --git a/docs/tech/architecture.md b/docs/tech/architecture.md index 5a9b64d..b44e081 100644 --- a/docs/tech/architecture.md +++ b/docs/tech/architecture.md @@ -1,50 +1,592 @@ # Architecture -devx is a reusable Python package providing development and CI/CD tools for oblachno-oss projects. +devx is a reusable Python package providing development and CI/CD tools for +oblachno-oss projects. It is self-contained under `src/devx/` and never imports +from scripts outside the package. -## Package Structure +## Package structure ``` src/devx/ -├── __init__.py # Version (single source of truth) -├── cli.py # Click-based CLI entry point (devx command) -├── config.py # Configuration system (DEVX_ env vars) -├── api_clients.py # GiteaClient, VikunjaClient — HTTP API wrappers -├── gitea_cli.py # TeaCLI — wrapper around tea CLI with JSON parsing -├── i18n.py # Translation system (gettext-based, translations.json) -├── exceptions.py # Custom exception types (DevxError, APIError) -├── translations.json # Translation strings (en, bg, de, ru, zh) -├── ci/ # CI/CD automation modules -├── tools/ # Developer tooling modules -└── molecule/ # Optional molecule testing helpers +├── __init__.py # Version (single source of truth, read by setuptools) +├── cli.py # Click-based CLI entry point (devx command) +├── config.py # Configuration system (DEVX_ env vars, .env loading) +├── api_clients.py # GiteaClient, VikunjaClient — HTTP API wrappers +├── gitea_cli.py # TeaCLI — wrapper around tea CLI with JSON parsing +├── i18n.py # Translation system (JSON-based, translations.json) +├── exceptions.py # Custom exception types (DevxError, APIError) +├── opentofu.py # OpenTofu output helpers +├── translations.json # Translation strings (en, bg, de, ru, zh) +├── ci/ # CI/CD automation modules (run by workflows) +│ ├── __init__.py +│ ├── _shared.py # Shared utilities (get_latest_tag) +│ ├── release.py # Automated versioning, tagging, changelog +│ ├── publish.py # Build and publish to Gitea PyPI registry +│ ├── auto_merge.py # Squash-merge PRs with task ID validation +│ ├── classify_changes.py # User-facing vs workflow-only change detection +│ ├── detect_release_commit.py # Detect release commits on master +│ ├── validate_commit_msg.py # Conventional commit validation +│ ├── pr_review.py # Automated PR review +│ ├── post_merge.py # Vikunja task updates after merge +│ ├── sync_wiki.py # Sync documentation to Gitea wiki +│ ├── push_badges.py # Generate and push quality badges +│ ├── notify_failure.py # Create Gitea issues on CI failures +│ ├── merge_junit.py # Merge JUnit XML reports from parallel runners +│ ├── distribute_files.py # Distribute files across parallel runners +│ ├── integration_guard.py # Run pytest with cross-runner fail-fast + JUnit +│ ├── discover_runners.py # Dynamic Gitea runner discovery +│ ├── check_translations.py # Translation completeness check +│ └── doc_coverage.py # Documentation coverage check +├── tools/ # Developer tooling modules (run locally or by CI) +│ ├── __init__.py +│ ├── setup.py # Environment setup (venv, deps, hooks, tea login) +│ ├── install_tools.py # Install actionlint, git-cliff, act_runner, tea +│ ├── check_test_speed.py # Measure unit test execution time +│ ├── configure_repo.py # Branch protection and label setup +│ ├── generate_badges.py # Badge SVG generation +│ ├── generate_cliff_config.py # Generate cliff.toml with correct prefix +│ └── install_checkmake.py # Install checkmake (Makefile linter) +└── molecule/ # Optional molecule testing helpers (Ansible projects) + ├── __init__.py + ├── discover_runners.py # Dynamic Gitea runner discovery + ├── distribute_molecule.py # Distribute scenarios across runners + ├── molecule_ci_guard.py # Run molecule with cross-runner fail-fast + ├── molecule_all.py # Run all molecule scenarios locally + ├── start_docker.py # Ensure Docker is available for molecule + └── platforms.py # Supported molecule platforms ``` -## Core Modules +## Core modules -### cli.py +### `__init__.py` -Click-based CLI entry point. Provides three command groups: `devx ci`, `devx tools`, `devx molecule`. Each subcommand delegates to the corresponding module via `_run_module()`. +Contains only `__version__`, the single source of truth for the package +version. Read by setuptools via `dynamic = ["version"]` in `pyproject.toml`. +Updated automatically by `devx.ci.release` during the release process. Treated +as infrastructure (not user-facing) by the change classifier since it is a +release artifact, not user code. -### i18n.py +### `cli.py` -Simple i18n system using a JSON translations file. Supports en, bg, de, ru, zh. Projects can extend translations by setting `DEVX_TRANSLATIONS_PATH` to a custom JSON file. +Click-based CLI entry point. Provides three command groups: `devx ci`, +`devx tools`, and `devx molecule`. Each subcommand delegates to the +corresponding module via `_run_module()`, which imports the module, sets +`sys.argv`, and calls its `main()` function. This design keeps all logic in +the modules themselves — `cli.py` is purely a router. -### exceptions.py +The CLI is registered as a console script via `pyproject.toml`: +```toml +[project.scripts] +devx = "devx.cli:cli" +``` -Custom exception hierarchy: `DevxError` (base), `APIError` (HTTP errors with status code and message). +### `config.py` -### api_clients.py +Shared configuration constants for all devx modules. All defaults can be +overridden via environment variables with the `DEVX_` prefix. Provides: -HTTP API clients with connection pooling and retry logic: -- `GiteaClient` — Gitea REST API (branch protection, labels, issues, PRs, releases, reviews) -- `VikunjaClient` — Vikunja REST API (tasks, projects, comments) +- `GITEA_API_URL` / `VIKUNJA_API_URL` — API endpoints +- `REPO_OWNER` — repository owner (must be set per-project) +- `TASK_PREFIX` / `TASK_ID_RE` — task ID prefix and regex (e.g., `DEVX-N`) +- `VIKUNJA_PROJECT_ID` — Vikunja project for task tracking +- `DEFAULT_TIMEOUT`, `DEFAULT_PER_PAGE` — HTTP client defaults +- `MAX_RETRIES`, `RETRY_BACKOFF_BASE`, `RETRY_STATUS_CODES` — retry config +- `CONVENTIONAL_RE` — conventional commit format regex -Both clients retry on transient errors (429, 5xx, connection errors) with exponential backoff. +### `exceptions.py` -### config.py +Custom exception hierarchy: -Configuration constants with env-var overrides (`DEVX_` prefix). Includes API URLs, timeouts, retry settings, task prefix regex, and conventional commit regex. +- `DevxError` — base exception for all devx errors +- `APIError(DevxError)` — raised when a REST API call returns an HTTP error. + Carries `status` (HTTP status code) and `message` (error message). -### gitea_cli.py +### `i18n.py` -Python wrapper around the `tea` Gitea CLI tool. Parses JSON output for structured data. Used by CI scripts for Gitea API operations (issues, labels, PRs, releases, reviews). +Simple i18n system using a JSON translations file (`translations.json`). +Supports five languages: `en`, `bg`, `de`, `ru`, `zh`. The `_()` function +wraps user-facing strings for translation. + +Projects can extend translations by setting `DEVX_TRANSLATIONS_PATH` to a +custom JSON file. Keys from the project's file are merged on top of devx's +built-in translations, allowing projects to override or add keys without +modifying the package. + +### `api_clients.py` + +Reusable HTTP API clients with connection pooling and retry logic. Both +clients retry on transient errors (429, 5xx, connection errors) with +exponential backoff (2s, 4s, 8s). + +**`GiteaClient`** — Gitea REST API wrapper: +- Branch protection (get, create, update) +- Labels (list, create, add to issues) +- Issues (create, list) +- Pull requests (get commits, merge, create review) +- Releases (list) +- Wiki pages (list, fetch, create, update, delete) + +**`VikunjaClient`** — Vikunja REST API wrapper: +- Tasks (list project tasks, get, update, mark done) +- Comments (create) + +### `gitea_cli.py` + +Thin Python wrapper around the `tea` Gitea CLI tool. Parses JSON output for +structured data. Used by CI scripts for Gitea API operations that tea handles +well, avoiding hand-rolled HTTP requests. + +**`TeaCLI`** operations: +- `create_issue()` — Create issues with labels +- `list_labels()` / `create_label()` / `add_label()` — Label management +- `create_pr()` / `merge_pr()` / `review_pr()` — Pull request operations +- `create_release()` / `list_releases()` — Release management +- `list_branches()` — Branch listing + +Operations NOT supported via tea (still use `GiteaClient`): +- Wiki page management +- Commit status checks +- Runner discovery +- PR file/commit listing (tea has limited support) +- Branch protection with detailed config + +### `opentofu.py` + +OpenTofu output helpers for CI/CD deployment scripts. Provides reusable +functions for extracting values from `tofu output` in a structured way, +eliminating duplicated `subprocess.run` boilerplate: + +- `get_tofu_output(output_name, cwd, env)` — Run `tofu output -json` and return parsed JSON +- `get_tofu_vm_ip(output_name, vm_name, cwd, env)` — Extract a VM's IP address +- `get_tofu_vm_field(output_name, vm_name, field, cwd, env)` — Extract a VM field + +## CI/CD modules (`devx.ci`) + +Modules in this package are run by Gitea Actions workflows. They may import +from `devx.api_clients`, `devx.config`, `devx.gitea_cli`, and `devx.i18n`. + +### `release.py` + +Automated release using git-cliff. Calculates the next semver version from +conventional commits since the last tag, updates `__version__` in +`__init__.py` and `CHANGELOG.md`, runs lint and tests to verify the release +is healthy, commits with `release: vX.Y.Z [skip ci]`, creates an annotated +tag, and pushes both to master. + +Idempotent: if there are no new conventional commits since the last tag, it +exits without doing anything. If the tag already exists, it skips tag creation +and only pushes. Includes a `--verify` mode that checks tag/version/changelog +alignment without making changes. + +### `publish.py` + +Builds the Python package with `python -m build`, publishes to a Gitea PyPI +registry (or standard PyPI if `PYPI_TOKEN` is set), and creates a Gitea +release with git-cliff-generated notes. Supports `--skip-build` for non-Python +repos that only need a Gitea release. + +### `auto_merge.py` + +Auto-merges a PR when all CI checks pass. Reads the task ID from the branch +name (falling back to `.taskid` file), validates the PR title format against +the Vikunja task title, extracts the conventional commit message from PR +commits, and squash-merges with title `{PREFIX}-N `. + +If the head branch is behind master (HTTP 405), it automatically pulls master, +rebases, force-pushes, and retries the merge. + +### `classify_changes.py` + +Classifies git changes between two refs as user-facing or workflow-only. Uses +a layered rule system configured in `pyproject.toml` under +`[tool.devx.classify]`: + +1. **User-facing overrides** (highest priority — safety override) +2. **Infrastructure overrides** (explicit per-file) +3. **Infrastructure patterns** (DEFAULT_INFRASTRUCTURE + project-specific) +4. **Default**: user-facing (safe default — any unknown file triggers release) + +Also supports custom tags (orthogonal to release impact) for CI conditional +execution (e.g., `ansible` tag to trigger molecule tests). + +### `pr_review.py` + +Automated PR review. Fetches the PR diff via the Gitea API and runs a series +of checks, posting a structured review with `COMMENT` (no issues) or +`REQUEST_CHANGES` (issues found): + +- Architecture compliance (no subprocess in CLI, no hardcoded URLs) +- Best practices (no `print()`, no bare `except`, no `TODO`/`FIXME`, no + functions > 50 lines) +- Security (no hardcoded secrets, no `shell=True`, no `eval`/`exec`) +- i18n (no raw strings in `click.echo()` without `_()` wrapper) +- Resource management (no `open()` without `with`, no `Popen()` without cleanup) +- Documentation (source changes must include doc updates) +- Test coverage (source changes must include test updates) +- Commit conventions (conventional commit format on PR commits) + +### `sync_wiki.py` + +Syncs documentation from `docs/` to the Gitea wiki via the API. Reads +`docs/mapping.json` to map file paths to wiki page titles, then creates or +updates pages. Supports `--dry-run`, `--verify` (check content), and +`--strict` (full integrity check: page count, missing pages, stale pages, +content match). + +### `push_badges.py` + +Generates SVG badge files using `devx.tools.generate_badges`, pushes them to +an orphan `badges` branch, and updates `README.md` and `docs/index.md` on +master with cache-busting `raw/commit//badge.svg` URLs (Gitea caches +`raw/branch/` URLs for 6 hours). Fetches latest master before generating +badges so the version badge reflects the current state. Supports `--retries` +for retrying on git push failures. + +### `notify_failure.py` + +Creates a Gitea issue when a CI workflow fails. Uses the `tea` CLI for issue +creation with failure labels. Supports `--auto-login` to configure the tea +CLI login profile from `REPO_TOKEN` and `DEVX_GITEA_API_URL` before creating +the issue. + +### `post_merge.py` + +Updates the Vikunja task after a merge to master. Extracts the task ID from +the commit message, marks the task as done, and posts a comment with the +merge SHA. + +### `validate_commit_msg.py` + +Validates commit messages. On feature branches: conventional commits only +(no `{PREFIX}-N` prefix). On master: must have `{PREFIX}-N` prefix from +auto-merge, followed by a conventional commit message. + +### `detect_release_commit.py` + +Detects whether the latest git commit is a release commit +(`release: vX.Y.Z [skip ci]`). Writes `is-release=true` or `is-release=false` +to `$GITHUB_OUTPUT` for use in CI workflow conditionals. + +### `check_translations.py` + +Validates translation files against the Python source code. Checks for +missing keys (used in code but not in translations), dead keys (defined but +not used), and missing languages (a key exists but is missing one of the five +supported languages). Supports checking additional translation sets via +`--translations`. + +### `doc_coverage.py` + +Checks documentation coverage for CLI commands and major modules. Parses +Click commands from `cli.py` and verifies each has documentation in +`docs/user/cli-commands.md`. Checks that core modules are documented in +`architecture.md` and CI scripts in `ci-cd-workflow.md`. Supports +`--fail-on-missing` to enforce 100% coverage. + +### `discover_runners.py` + +Discovers available Gitea Actions runners at three levels: repository, +organization, and instance (admin). Falls back to the `MOLECULE_RUNNERS` repo +variable or `DEFAULT_MAX_RUNNERS` (3). Outputs runner count or a JSON index +array for use as a dynamic matrix in Gitea Actions. + +### `distribute_files.py` + +Distributes files matching a glob pattern across N parallel runners +(round-robin). Writes the assigned file list for the current runner to +`$GITHUB_ENV`. Used for splitting test suites across CI runners. + +### `merge_junit.py` + +Merges JUnit XML reports from parallel matrix runners into a single +consolidated report. Exit code is non-zero if any merged suite reports +failures, making it suitable as a CI gating step. + +### `integration_guard.py` + +Runs pytest with the same cross-runner failure detection mechanism used by +`molecule_ci_guard`. If any other integration-tests matrix runner reports +failure, the current pytest subprocess is killed and this runner exits early. +Generates JUnit XML via pytest's `--junitxml` flag. + +## Developer tools (`devx.tools`) + +Modules in this package are run locally or by CI setup jobs. They may import +from `devx.api_clients`, `devx.config`, and `devx.gitea_cli`. + +### `setup.py` + +Project setup: installs Python dependencies (editable mode with extras), +Ansible Galaxy collections (if `ansible/requirements.yml` exists), pre-commit +hooks (pre-commit, commit-msg, pre-push), and configures the `tea` CLI login +profile from `.env`. Supports `--extras` to specify dependency groups, +`--no-pre-commit` to skip hook installation, and `--no-tea-login` to skip tea +configuration. + +### `install_tools.py` + +Installs CI/CD development tools that are not Python packages: actionlint, +git-cliff, act_runner, and tea. Each tool is installed to `~/.local/bin` if +not already on PATH. Idempotent: skips tools that are already available. +Supports `--tool` to install specific tools and `--list` to show status. + +### `check_test_speed.py` + +Runs unit tests and enforces execution-time budgets. Two quality gates: +total suite time must not exceed `--max-seconds` (default: 10s), and no +individual test may exceed `--max-single-seconds` (default: 0.5s, 0 to +disable). Runs `make test-unit` with `PYTEST_ADDOPTS=--durations=0`. + +### `configure_repo.py` + +Configures repository branch protection and labels via the Gitea REST API. +Sets up master branch protection (required status checks, block on rejected +reviews, block on outdated branch) and creates standard labels. Status check +contexts are read from `DEVX_STATUS_CHECKS` or default to +`CI / quality (pull_request)`. + +### `generate_badges.py` + +Generates self-contained SVG badge files from project metrics. Runs +pytest-cov, doc-coverage, lint checks, and version extraction, then writes +SVG files that can be served as static files from the Gitea raw file API. +Badges generated: coverage, tests, docs, quality, version, python. + +### `generate_cliff_config.py` + +Generates a `cliff.toml` configuration file with the correct task ID prefix +preprocessor. Eliminates the need to manually duplicate and maintain +`cliff.toml` across repos that use devx. Supports `--prefix` to set the task +ID prefix and `--force` to overwrite an existing file. + +### `install_checkmake.py` + +Installs checkmake (Makefile linter) if not already present. Tries +`go install` first if Go is available, otherwise downloads the latest +pre-built Linux binary from the official GitHub releases. + +## Molecule modules (`devx.molecule`) + +Optional modules for projects with Ansible roles. Requires the `molecule` +extra (`pip install devx[molecule]`). + +### `distribute_molecule.py` + +Distributes molecule (scenario, platform) pairs across N parallel runners. +Discovers scenarios under `ansible/roles/*/molecule/` and crosses them with +the supported OS platform matrix. Supports `--roles-root` for multi-role +repositories, `--list` to list scenarios, and `--list-platforms` to list +platforms. + +### `molecule_ci_guard.py` + +Runs molecule tests sequentially while polling the Gitea API for other runner +failures. If any other molecule matrix runner reports failure, the current +molecule subprocess is killed and this runner exits early. Generates JUnit +XML when `--junit-output` is provided. Supports both single-role (4-part) and +multi-role (5-part) pair encoding. + +### `molecule_all.py` + +Runs all molecule scenarios on all supported OS platforms sequentially. +Intended for local development; CI uses the parallel matrix instead. + +### `discover_runners.py` + +Discovers available Gitea Actions runners for molecule tests. Same logic as +`devx.ci.discover_runners` but intended for molecule-specific workflows. + +### `start_docker.py` + +Ensures Docker is available for molecule tests in CI. Verifies Docker is +accessible and sets `DOCKER_HOST` explicitly. If the host socket is not +available, tries the rootless socket, then starts a local `dockerd` with the +vfs storage driver (requires privileged container). + +### `platforms.py` + +Single source of truth for the supported OS platform matrix. Each entry maps +a short name to (image, command). Uses the project's pre-built +molecule-test-base image with `sleep infinity` (not systemd) to avoid cgroup +v2 failures. Supports loading custom platforms from a JSON file. + +## Design principles + +- **Self-contained package** — `src/devx/` never imports from scripts outside + the package. This allows devx to be installed and used as a dependency + without requiring a specific repo layout in the consumer. +- **Module-based invocation** — All tools invoked via `python -m devx.ci.*`, + `python -m devx.tools.*`, or `python -m devx.molecule.*`. The `devx` CLI is + a thin router that delegates to module `main()` functions. +- **PYTHONPATH: src** — Workflows set `PYTHONPATH: src` (not `.:src` since + there are no scripts at repo root). The `src` directory is the sole import + root. +- **Config via env vars** — `DEVX_*` environment variables with `.env` file + fallback. Projects override defaults via environment or `.env`, never by + editing package code. +- **100% test coverage** — enforced by `--cov-fail-under=100` in pytest. +- **i18n by default** — all user-facing strings wrapped in `_()` for + translation. Five languages supported out of the box. +- **Safe-by-default classification** — any file that doesn't match an + infrastructure pattern defaults to user-facing, triggering a release. This + prevents new file types from accidentally skipping releases. +- **Secrets via environment** — secrets are passed via environment variables, + never on the command line. + +## Import rules + +1. **`src/devx/` is self-contained** — the package never imports from outside `src/` +2. **CI modules** (`devx.ci.*`) may import from `devx.api_clients`, + `devx.config`, `devx.gitea_cli`, `devx.i18n` +3. **Tool modules** (`devx.tools.*`) may import from `devx.api_clients`, + `devx.config`, `devx.gitea_cli` +4. **Cross-module imports** within `devx.ci.*` or `devx.tools.*` are allowed + but must be documented (e.g., `release.py` imports from + `classify_changes.py`) + +## Data flow + +### PR lifecycle + +``` +Developer creates Vikunja task (DEVX-N) + │ + ▼ +Developer creates branch (DEVX-N-short-description) + │ + ▼ +Developer commits (conventional commits, no DEVX-N prefix) + │ + ▼ +Developer pushes and creates PR (title: "DEVX-N: ") + │ + ▼ +CI workflow (ci.yml) triggers: + │ + ├── quality (lint, tests, coverage, test speed, doc coverage, + │ translation check, dependency scan, workflow dry-run) + │ + ├── detect-changes (classify_changes.py → user-facing or workflow-only) + │ └── if user-facing → release-dry-run (release.py --dry-run) + │ + ├── pr-review (pr_review.py → posts COMMENT or REQUEST_CHANGES) + │ + └── auto-merge (auto_merge.py) + ├── validate PR title format + ├── validate PR title matches Vikunja task title + ├── extract conventional commit message from PR commits + ├── squash-merge with "DEVX-N " title + └── push to master + │ + ▼ + Post-merge workflow triggers (see below) +``` + +### Post-merge flow + +``` +Push to master (squash-merge commit: "DEVX-N ") + │ + ▼ +Post-merge workflow (post-merge.yml) triggers: + │ + ├── detect-type (detect_release_commit.py) + │ └── is-release? → skip all jobs except badges + │ + ├── validate-commit-msg (validate_commit_msg.py --branch master) + │ + ├── release (release.py) + │ ├── classify_changes.py → skip if workflow-only + │ ├── git-cliff → calculate next version + │ ├── update __version__ in __init__.py + │ ├── update CHANGELOG.md + │ ├── run make lint-ruff && make pytest-cov + │ ├── commit "release: vX.Y.Z [skip ci]" + │ ├── create annotated tag vX.Y.Z + │ └── push commit + tag to master + │ │ + │ ▼ + │ Tag push triggers publish workflow (see below) + │ + ├── sync-wiki (sync_wiki.py --strict) + │ └── sync docs/ to Gitea wiki with integrity check + │ + ├── badges (push_badges.py) [ALWAYS runs, even on release commits] + │ ├── fetch latest master + │ ├── generate_badges.py → SVG files + │ ├── push to orphan badges branch + │ └── update README.md + docs/index.md with cache-busting URLs + │ + ├── vikunja (post_merge.py) + │ ├── extract task ID from commit message + │ ├── mark Vikunja task as done + │ └── post comment with merge SHA + │ + └── configure-repo (configure_repo.py) + └── ensure branch protection and labels +``` + +### Publish flow + +``` +Tag push (vX.Y.Z) triggers publish workflow (publish.yml): + │ + ▼ + ├── install build, twine, git-cliff, tea + ├── configure tea login + │ + └── publish (publish.py) + ├── build package (python -m build) + ├── publish to Gitea PyPI registry (twine upload) + │ OR publish to standard PyPI (if PYPI_TOKEN set) + │ OR skip publish (if --skip-build) + └── create Gitea release with git-cliff notes +``` + +### Badge generation flow + +``` +push_badges.py: + │ + ├── fetch_latest_master() → git fetch + reset --hard origin/master + │ + ├── generate_badges() → devx.tools.generate_badges + │ ├── run pytest-cov → parse coverage % + │ ├── run pytest → parse test count + │ ├── run doc_coverage → parse doc coverage % + │ ├── run lint → quality status + │ ├── read __version__ from __init__.py + │ └── write SVG files to .badges/ + │ + ├── push_to_badges_branch() + │ ├── git checkout --orphan badges + │ ├── git rm -rf . + │ ├── copy SVG files to root + │ ├── git commit "Update badges [skip ci]" + │ ├── git push origin badges --force + │ └── return commit SHA + │ + └── update_readme_with_badge_sha() + ├── git checkout master + ├── replace raw/branch/badges/ URLs with raw/commit// URLs + ├── git commit "chore: update badge URLs [skip ci]" + └── git push origin master +``` + +## tea CLI integration + +The `tea` Gitea CLI tool is used for Gitea API interactions where tea provides +reliable, official support. It is installed by +`python -m devx.tools.install_tools` and configured by +`python -m devx.tools.setup` (login profile from `.env` `REPO_TOKEN`). + +`devx.gitea_cli.TeaCLI` wraps tea with JSON output parsing. Operations that +tea does not support (wiki management, commit status, runner discovery, +detailed branch protection) fall back to `GiteaClient` (direct HTTP). + +## Version source + +The version source is `__version__` in `src/devx/__init__.py`, read by +setuptools via `dynamic = ["version"]` in `pyproject.toml`. The release +script updates this file, commits it, and tags the commit. This ensures the +package version, git tag, and changelog always stay aligned. diff --git a/docs/tech/ci-cd-workflow.md b/docs/tech/ci-cd-workflow.md index c5a7132..085969e 100644 --- a/docs/tech/ci-cd-workflow.md +++ b/docs/tech/ci-cd-workflow.md @@ -1,85 +1,557 @@ # CI/CD Workflow -devx uses Gitea Actions for CI/CD automation. The workflow replicates GRM's automated pipeline but without molecule tests. +devx uses Gitea Actions for CI/CD automation. Three workflows implement a +complete pipeline: pull request validation, post-merge release automation, and +tag-triggered publishing. -## Workflows +## Workflow overview -### CI (`ci.yml`) +``` +PR opened/synchronized ──► CI (ci.yml) + │ ├── quality + │ ├── detect-changes + │ ├── release-dry-run (if user-facing) + │ ├── pr-review + │ └── auto-merge ──► squash-merge to master + │ │ + ▼ ▼ +Push to master ──► Post-merge (post-merge.yml) + ├── detect-type + ├── validate-commit-msg + ├── release ──► tag vX.Y.Z + ├── sync-wiki │ + ├── badges │ + ├── vikunja │ + └── configure-repo │ + │ + ▼ +Tag push (v*) ──► Publish (publish.yml) + └── publish ──► Gitea PyPI registry + Gitea release +``` -Runs on pull requests. Jobs: +## CI workflow (`ci.yml`) -1. **quality** — lint (ruff, pyright, bandit, actionlint), unit tests with 100% coverage, test speed check, doc coverage, translation check, dependency scan -2. **detect-changes** — classify changes as user-facing or workflow-only -3. **release-dry-run** — dry-run the release script (only if user-facing changes) -4. **pr-review** — automated PR review -5. **auto-merge** — squash-merge PR when all checks pass +Runs on pull requests (opened and synchronize) and manual dispatch. -### Post-merge (`post-merge.yml`) +### Jobs -Runs on every push to master. Jobs: +#### `quality` -1. **detect-type** — check if commit is a release commit -2. **validate-commit-msg** — validate conventional commit format -3. **release** — calculate next version, update changelog, tag, push -4. **sync-wiki** — sync docs to Gitea wiki -5. **badges** — generate and push quality badges -6. **vikunja** — mark Vikunja task as done -7. **configure-repo** — ensure branch protection and labels +The main quality gate. Runs on every PR: -### Publish (`publish.yml`) +1. **Lint all** — ruff check, ruff format check, pyright, bandit, actionlint + (via `make lint-all`) +2. **Unit tests with 100% coverage** — `make pytest-cov` +3. **Check unit test speed** — `python -m devx.tools.check_test_speed + --max-seconds 4 --max-single-seconds 0.5` +4. **Documentation coverage check** — `python -m devx.ci.doc_coverage + --fail-on-missing` +5. **Translation completeness check** — `python -m devx.ci.check_translations` +6. **Dependency security scan** — `pip-audit --desc --skip-editable` + (best-effort, non-blocking) +7. **Workflow dry-run validation** — `make workflow-dryrun` via act_runner + (best-effort, skipped if act_runner is not installed) -Runs on tag pushes (`v*`). Builds the package, publishes to Gitea PyPI registry, and creates a Gitea release. +#### `detect-changes` -## CI Scripts +Classifies changes between `origin/master` and the PR head as user-facing or +workflow-only using `python -m devx.ci.classify_changes --github-output`. +Writes `user-facing-changed=true|false` to the job output for use by +downstream jobs. -### auto_merge.py +#### `release-dry-run` -Auto-merge PR when all CI checks pass. Reads task ID from `.taskid`, validates PR title format, checks Vikunja task exists, squash-merges with `DEVX-N ` title. +Depends on `quality` and `detect-changes`. Only runs if user-facing changes +are detected. Runs `python -m devx.ci.release --dry-run` to validate that +the release script can calculate the next version and generate the changelog +without making changes. Non-blocking (uses `|| true`). -### release.py +#### `pr-review` -Automated release using git-cliff. Calculates next semver version from conventional commits, updates `__version__` in `__init__.py`, updates `CHANGELOG.md`, runs lint and tests, commits with `release: vX.Y.Z [skip ci]`, creates annotated tag, pushes. +Runs on every pull request. Executes `python -m devx.ci.pr_review` with the +PR number and repository. Fetches the PR diff via the Gitea API and runs +automated checks, posting a structured review: -### publish.py +- `COMMENT` — no issues found +- `REQUEST_CHANGES` — issues found that must be addressed -Builds package with `python -m build`, publishes to Gitea PyPI registry via twine, creates Gitea release with git-cliff-generated notes. +Checks performed: +1. Architecture compliance — no subprocess in CLI, no hardcoded URLs +2. Best practices — no `print()`, no bare `except`, no `TODO`/`FIXME`, + no functions > 50 lines +3. Security — no hardcoded secrets, no `shell=True`, no `eval`/`exec` +4. i18n — no raw strings in `click.echo()` without `_()` wrapper +5. Resource management — no `open()` without `with`, no `Popen()` without + cleanup +6. Documentation — source changes must include doc updates +7. Test coverage — source changes must include test updates +8. Commit conventions — conventional commit format on PR commits -### pr_review.py +#### `auto-merge` -Automated PR review. Checks architecture compliance, best practices, security, i18n, resource management, documentation, test coverage, and commit conventions. Posts inline comments and structured review. +Depends on `quality`, `detect-changes`, and `pr-review`. The final job in the +CI workflow. Runs `python -m devx.ci.auto_merge` with the branch name, PR +title, repository, and PR number: -### notify_failure.py +1. **Read task ID** from branch name (e.g., `DEVX-12-fix-foo` → `DEVX-12`), + falling back to `.taskid` file for branches without a task ID prefix +2. **Validate PR title format** — must be `{PREFIX}-N: ` +3. **Validate PR title matches Vikunja task** — fetches the Vikunja task and + compares the title +4. **Extract conventional commit message** from PR commits (newest matching + conventional format) +5. **Squash-merge** with title `{PREFIX}-N ` +6. If the head branch is behind master (HTTP 405), automatically pulls master, + rebases, force-pushes, and retries the merge -Creates a Gitea issue when a CI workflow fails. Uses tea CLI for issue creation with failure labels. +The merge commit push to master triggers the post-merge workflow. -### post_merge.py +### Smart CI: user-facing vs workflow-only changes -Updates Vikunja task after a merge to master. Extracts task ID from commit message, marks task as done, posts a comment with the merge SHA. +Not all changes require a new release. The `detect-changes` job classifies +changes using `python -m devx.ci.classify_changes`: -### classify_changes.py +**Workflow-only paths** (infrastructure — no release needed): +- `.gitea/**` — Gitea Actions workflows +- `tests/**` — Test files +- `AGENTS.md`, `README.md`, `CHANGELOG.md` — Project docs +- `Makefile`, `cliff.toml`, `.pre-commit-config.yaml` — Config +- `.env.example`, `.gitignore` — Config +- `hooks/**` — Git hooks +- `src/devx/__init__.py` — Only contains `__version__` (release artifact) -Classifies git changes as user-facing or workflow-only. Used to skip releases for infrastructure-only changes. Patterns are configurable. +**User-facing paths** (tool changes — release needed) — everything else: +- `src/devx/**` — Python package source (except `__init__.py`) +- `pyproject.toml` — Package metadata +- Any new file type not in the allowlist -### discover_runners.py +Classification is configured in `pyproject.toml` under +`[tool.devx.classify]`. The framework provides `DEFAULT_INFRASTRUCTURE` — a +curated list of paths that are infrastructure for any Python project. Projects +inherit these automatically and only specify what is different. -Discovers available Gitea Actions runners at repo, org, and instance levels. Generates a dynamic matrix for parallel job distribution. +Rule priority (first match wins): +1. `user_facing_overrides` — safety override (highest priority) +2. `infrastructure_overrides` — explicit per-file +3. `infrastructure` — DEFAULT_INFRASTRUCTURE + project-specific patterns +4. Default: user-facing (safe — any unknown file triggers release) -### detect_release_commit.py +## Post-merge workflow (`post-merge.yml`) -Detects whether the latest git commit is a release commit. Writes `is-release=true` or `is-release=false` to GitHub output. +Runs on every push to master. A single workflow with conditional jobs +replaces separate workflows for release, wiki sync, badges, and Vikunja task +updates. -### push_badges.py +### Job dependency graph -Generates SVG badge files from project metrics (tests, coverage, quality, version). Pushes to `badges` branch and updates README with cache-busting commit SHA URLs. +``` +detect-type ──┬── validate-commit-msg (skip if release commit) + ├── release (skip if release commit) + │ │ + │ ├── sync-wiki (needs release) + │ ├── badges (needs release, ALWAYS runs) + │ └── vikunja (needs release) + └── configure-repo (independent, skip if release commit) +``` -### distribute_molecule.py +`sync-wiki` and `vikunja` depend on `release` succeeding so that the wiki and +task tracker are only updated when the code is actually released. If release +fails, they are skipped to avoid leaving the wiki or Vikunja in an +inconsistent state. -Distributes molecule (scenario, platform) pairs across N parallel runners. Discovers scenarios under `ansible/roles/*/molecule/`. +The `badges` job uses `if: always()` with no is-release condition so it runs +on every push to master, including release commits. This ensures badges +(tests, coverage, version, etc.) are always current. -### molecule_ci_guard.py +When `release` creates a `release: vX.Y.Z` commit, the release commit's +post-merge run still updates badges (the version badge picks up the new +version). Other jobs skip. The tag push triggers `publish.yml`. -Runs molecule tests sequentially while polling Gitea for other runner failures. Aborts if another runner fails the same job. +### Jobs -### validate_commit_msg.py +#### `detect-type` -Validates commit messages. On feature branches: conventional commits only (no `DEVX-N` prefix). On master: must have `DEVX-N` prefix from auto-merge. +Checks if the latest commit is a release commit (`release: vX.Y.Z [skip ci]`) +using `python -m devx.ci.detect_release_commit`. Writes `is-release=true` or +`is-release=false` to the job output. All subsequent jobs use this to +conditionally skip for release commits. + +#### `validate-commit-msg` + +Depends on `detect-type`. Skips for release commits. Validates the latest +commit message using `python -m devx.ci.validate_commit_msg --branch master`. +On master, commits must follow `{PREFIX}-N: ` format +(added by auto-merge). + +#### `release` + +Depends on `detect-type`. Skips for release commits. The core release +automation job. Runs `python -m devx.ci.release`: + +1. **Classify changes** — calls `classify_changes.py` to check for user-facing + changes. If only infrastructure files changed, exits without releasing. +2. **Calculate next version** — uses git-cliff to determine the next semver + version from conventional commits since the last tag +3. **Update version file** — updates `__version__` in `src/devx/__init__.py` +4. **Update changelog** — prepends the new version section to `CHANGELOG.md` + using git-cliff output +5. **Run tests** — executes `make lint-ruff` and `make pytest-cov` to verify + the release is healthy. If either fails, the release is aborted — no + commit, no tag. Use `--skip-tests` only for emergency releases. +6. **Commit** — stages the version file and changelog, commits with + `release: vX.Y.Z [skip ci]` (uses `--no-verify` to bypass the commit-msg + hook since release commits are a special case) +7. **Create tag** — creates an annotated tag `vX.Y.Z` with the changelog as + the tag message +8. **Push** — pushes both the commit and tag to master + +The script is idempotent: if there are no new conventional commits since the +last tag, it exits without doing anything. If the tag already exists (e.g., +from a partial previous run), it skips tag creation and only pushes. + +**Tag consistency**: Before releasing, the script fetches remote tags and +verifies all existing tags point to commits whose message matches the tag +version. This prevents duplicate release commits and ensures +tag/version/commit alignment. + +**Version bumping rules** (git-cliff): + +| Commit type | Version bump | +|-------------|-------------| +| `feat:` | minor (0.X.0) | +| `fix:` | patch (0.0.X) | +| `feat!:` or `BREAKING CHANGE` | minor (pre-1.0) | +| `chore:`, `ci:`, `docs:` | no bump (excluded by cliff.toml) | + +On failure, the `notify_failure` step creates a Gitea issue via +`python -m devx.ci.notify_failure`. + +#### `sync-wiki` + +Depends on `detect-type` and `release`. Skips for release commits. Syncs +documentation from `docs/` to the Gitea wiki using +`python -m devx.ci.sync_wiki --repo --strict`: + +1. Reads `docs/mapping.json` to map file paths to wiki page titles +2. Lists existing wiki pages via the Gitea API +3. For each mapped file, reads content and creates or updates the wiki page +4. `--strict` runs a full integrity check: verifies page count, missing + pages, stale pages, and content match. Fails if any page is empty or + content doesn't match. + +Pages that exist in the wiki but not in the mapping are left untouched (not +deleted). + +On failure, the `notify_failure` step creates a Gitea issue. + +#### `badges` + +Depends on `detect-type` and `release`. Uses `if: always()` so it runs on +every push to master, including release commits. Generates and pushes quality +badges using `python -m devx.ci.push_badges`: + +1. **Fetch latest master** — `git fetch origin master && git reset --hard + origin/master` (ensures the version badge reflects the current state, + even if the release job just pushed a new version) +2. **Generate badges** — calls `devx.tools.generate_badges` which runs + pytest-cov, doc-coverage, lint checks, and version extraction, then writes + SVG files: `coverage.svg`, `tests.svg`, `docs.svg`, `quality.svg`, + `version.svg`, `python.svg` +3. **Push to badges branch** — creates an orphan `badges` branch, copies SVG + files, commits, and force-pushes +4. **Update README/docs** — switches back to master, replaces + `raw/branch/badges/.svg` URLs with `raw/commit//.svg` + URLs (cache-busting — Gitea caches `raw/branch/` URLs for 6 hours), + commits, and pushes + +Supports `--retries` for retrying on git push failures (fetches latest master +and waits 10s between attempts). + +On failure, the `notify_failure` step creates a Gitea issue. + +#### `vikunja` + +Depends on `detect-type` and `release`. Skips for release commits. Updates +the Vikunja task after a merge using `python -m devx.ci.post_merge --git-sha +`: + +1. Extracts the task ID from the first line of the commit message +2. Marks the corresponding Vikunja task as done +3. Posts a comment with the merge SHA + +On failure, the `notify_failure` step creates a Gitea issue. + +#### `configure-repo` + +Depends on `detect-type`. Skips for release commits. Ensures branch +protection and labels are configured using +`python -m devx.tools.configure_repo --repo --owner `: + +- Sets up master branch protection (required status checks, block on rejected + reviews, block on outdated branch) +- Creates standard labels +- Status check contexts read from `DEVX_STATUS_CHECKS` or default to + `CI / quality (pull_request)` + +On failure, the `notify_failure` step creates a Gitea issue. + +## Publish workflow (`publish.yml`) + +Runs on tag pushes matching `v*`. Triggered by the `release` job in the +post-merge workflow when it creates and pushes a new version tag. + +### Job: `publish` + +1. **Install dependencies** — build, twine, requests, python-dotenv, click, + and the project itself +2. **Install CI tools** — git-cliff and tea via + `python -m devx.tools.install_tools` +3. **Configure tea login** — `tea login add` using `REPO_TOKEN` +4. **Build and publish** — `python -m devx.ci.publish `: + - Build the package with `python -m build` + - Publish to the Gitea PyPI registry (default) using `twine upload + --repository-url -u -p ` + - OR publish to standard PyPI if `PYPI_TOKEN` is set + - OR skip publishing if `--skip-build` is passed (non-Python repos) + - Create a Gitea release with git-cliff-generated release notes via + `tea create release` + +Publishing destination resolution (checked in order): +1. **Gitea PyPI registry** — if `--registry-url` is given, or + `DEVX_PYPI_REGISTRY_URL` env var is set, or derived from `GITEA_API_URL` +2. **Standard PyPI** — if `PYPI_TOKEN` is set (takes precedence over Gitea + registry) +3. **Skip** — if neither is configured, only the Gitea release is created + +On failure, the `notify_failure` step creates a Gitea issue. + +## CI scripts + +### `auto_merge.py` + +Auto-merge PR when all CI checks pass. Reads task ID from the branch name +(e.g., `DEVX-12-fix-foo` → `DEVX-12`), falling back to `.taskid` file for +branches without a task ID prefix. Validates PR title format, checks the +Vikunja task exists and the title matches, extracts the conventional commit +message from PR commits, and squash-merges with +`{PREFIX}-N ` title. + +```bash +python -m devx.ci.auto_merge +``` + +### `release.py` + +Automated release using git-cliff. Calculates next semver version from +conventional commits, updates `__version__` and `CHANGELOG.md`, runs lint and +tests, commits with `release: vX.Y.Z [skip ci]`, creates annotated tag, and +pushes. Idempotent — exits if no unreleased changes. + +```bash +python -m devx.ci.release [--dry-run] [--skip-tests] [--verify] +``` + +- `--dry-run` — preview without making changes +- `--skip-tests` — skip lint and test verification (emergency only) +- `--verify` — check tag/version/changelog alignment and exit + +### `publish.py` + +Builds package, publishes to Gitea PyPI registry or standard PyPI, and +creates a Gitea release with git-cliff-generated notes. + +```bash +python -m devx.ci.publish [--registry-url ] [--skip-build] +``` + +### `pr_review.py` + +Automated PR review. Fetches the PR diff via the Gitea API, runs automated +checks (architecture, best practices, security, i18n, resource management, +documentation, test coverage, commit conventions), and posts a structured +review with inline comments. + +```bash +python -m devx.ci.pr_review +``` + +### `notify_failure.py` + +Creates a Gitea issue when a CI workflow fails. Uses the tea CLI for issue +creation with failure labels. Supports `--auto-login` to configure the tea +CLI login profile from `REPO_TOKEN`. + +```bash +python -m devx.ci.notify_failure --repo --run-id \ + --workflow --commit [--auto-login] +``` + +### `post_merge.py` + +Updates Vikunja task after a merge to master. Extracts task ID from the +commit message, marks the task as done, and posts a comment with the merge SHA. + +```bash +python -m devx.ci.post_merge [--commit-sha ] [--git-sha ] +``` + +### `classify_changes.py` + +Classifies git changes as user-facing or workflow-only. Uses a layered rule +system configured in `pyproject.toml`. Safe-by-default: any unknown file +defaults to user-facing. + +```bash +python -m devx.ci.classify_changes [--base ] [--head ] \ + [--quiet] [--check ] [--github-output] +``` + +### `discover_runners.py` + +Discovers available Gitea Actions runners at repository, organization, and +instance levels. Falls back to `MOLECULE_RUNNERS` repo variable or +`DEFAULT_MAX_RUNNERS` (3). + +```bash +python -m devx.ci.discover_runners --owner --repo [--count] [--indices] +``` + +### `detect_release_commit.py` + +Detects whether the latest git commit is a release commit. Writes +`is-release=true|false` to `$GITHUB_OUTPUT`. + +```bash +python -m devx.ci.detect_release_commit +``` + +### `push_badges.py` + +Generates SVG badge files, pushes them to the `badges` branch, and updates +README.md and docs/index.md with cache-busting `raw/commit//` URLs. + +```bash +python -m devx.ci.push_badges [--output-dir ] [--branch ] \ + [--no-readme-update] [--retries ] +``` + +### `distribute_molecule.py` + +Distributes molecule (scenario, platform) pairs across N parallel runners. +Discovers scenarios under `ansible/roles/*/molecule/`. + +```bash +python -m devx.molecule.distribute_molecule --runner-index --max-runners +python -m devx.molecule.distribute_molecule --list +python -m devx.molecule.distribute_molecule --list-platforms +``` + +### `molecule_ci_guard.py` + +Runs molecule tests sequentially while polling the Gitea API for other runner +failures. Aborts early if another runner fails the same job. Generates JUnit +XML when `--junit-output` is provided. + +```bash +python -m devx.molecule.molecule_ci_guard [--roles-root ] \ + [--junit-output ] pair1 pair2 ... +``` + +### `validate_commit_msg.py` + +Validates commit messages. On feature branches: conventional commits only +(no `{PREFIX}-N` prefix). On master: must have `{PREFIX}-N` prefix from +auto-merge, followed by a conventional commit message. + +```bash +python -m devx.ci.validate_commit_msg [--branch ] +``` + +### `sync_wiki.py` + +Syncs documentation from `docs/` to the Gitea wiki via the API. Reads +`docs/mapping.json` for file-to-page mapping. Supports `--dry-run`, +`--verify`, and `--strict` (full integrity check). + +```bash +python -m devx.ci.sync_wiki [--dry-run] [--repo ] [--verify] [--strict] +``` + +### `check_translations.py` + +Validates translation files against the Python source code. Checks for +missing keys, dead keys, and missing languages. + +```bash +python -m devx.ci.check_translations [--translations ]... +``` + +### `doc_coverage.py` + +Checks documentation coverage for CLI commands and major modules. Parses +Click commands from `cli.py` and verifies documentation exists. + +```bash +python -m devx.ci.doc_coverage [--docs-dir ] [--fail-on-missing] +``` + +### `distribute_files.py` + +Distributes files matching a glob pattern across N parallel runners +(round-robin). Writes the assigned file list to `$GITHUB_ENV`. + +```bash +python -m devx.ci.distribute_files --pattern --runner-index \ + --max-runners [--github-env] [--skip-if-excess] +``` + +### `merge_junit.py` + +Merges JUnit XML reports from parallel matrix runners into a single +consolidated report. Exit code is non-zero if any merged suite reports +failures. + +```bash +python -m devx.ci.merge_junit --pattern --output +``` + +### `integration_guard.py` + +Runs pytest with cross-runner failure detection. If any other +integration-tests matrix runner reports failure, the current pytest +subprocess is killed and this runner exits early. + +```bash +python -m devx.ci.integration_guard --junit-output -- +``` + +## Release process summary + +The complete release process from PR to published package: + +1. **PR merged** — `auto-merge` squash-merges the PR to master with + `{PREFIX}-N ` title +2. **Post-merge triggers** — the merge push triggers `post-merge.yml` +3. **detect-type** — confirms the commit is not a release commit +4. **release** — `release.py` calculates the next version, updates files, + runs tests, commits `release: vX.Y.Z [skip ci]`, creates tag `vX.Y.Z`, + and pushes to master +5. **Tag push triggers publish** — the tag push triggers `publish.yml` +6. **publish** — `publish.py` builds the package, publishes to the Gitea PyPI + registry, and creates a Gitea release with git-cliff notes +7. **sync-wiki** — documentation is synced to the Gitea wiki +8. **badges** — quality badges are regenerated and pushed to the `badges` + branch; README and docs/index.md are updated with cache-busting URLs +9. **vikunja** — the corresponding Vikunja task is marked as done +10. **configure-repo** — branch protection and labels are ensured + +The release commit's post-merge run skips all jobs except `badges` (which +picks up the new version number). This prevents infinite loops. + +## Failure handling + +Every job in the post-merge and publish workflows has a `notify_failure` step +that runs `if: failure()`. This creates a Gitea issue with the workflow name, +run ID, and commit SHA, ensuring failures that would otherwise go unnoticed +in the Actions tab are surfaced as issues. The issue is created via the tea +CLI with a `bug` label if available. diff --git a/docs/user/cli-commands.md b/docs/user/cli-commands.md index 4168eff..3aaaede 100644 --- a/docs/user/cli-commands.md +++ b/docs/user/cli-commands.md @@ -1,134 +1,481 @@ # CLI Commands devx provides a CLI with three command groups: `ci`, `tools`, and `molecule`. +Each subcommand delegates to the corresponding Python module via +`python -m devx.*`, so `devx ci release` is equivalent to +`python -m devx.ci.release`. + +```bash +devx --help # show all command groups +devx --version # show package version +devx ci --help # show CI commands +devx tools --help # show tools commands +devx molecule --help # show molecule commands +``` ## CI Commands ### `devx ci auto-merge` -Auto-merge a PR when all CI checks pass. Validates PR title, checks Vikunja task, squash-merges. +Auto-merge a PR when all CI checks pass. Reads the task ID from the branch +name (falling back to `.taskid` file), validates the PR title format against +the Vikunja task title, extracts the conventional commit message from PR +commits, and squash-merges with `{PREFIX}-N ` title. + +If the head branch is behind master (HTTP 405), automatically pulls master, +rebases, force-pushes, and retries the merge. + +```bash +devx ci auto-merge +# Example: +devx ci auto-merge DEVX-12-add-feature "DEVX-12: Add feature" oblachno-oss/devx 42 +``` ### `devx ci check-translations` -Check translation files for gaps, dead keys, and missing languages. +Check translation files for gaps, dead keys, and missing languages. Validates +translation files against the Python source code that uses them. By default, +checks `src/devx/translations.json` against `src/devx/**/*.py`. + +Checks performed: +- **Missing keys** — a `_()` call in code has no entry in the translations file +- **Dead keys** — a key in the translations file is not used in any code +- **Missing languages** — a key exists but is missing one of the five + supported languages (en, bg, de, ru, zh) + +```bash +devx ci check-translations +devx ci check-translations --translations path/to/translations.json +``` ### `devx ci classify-changes` -Classify git changes as user-facing or workflow-only. Used to skip releases for infrastructure-only changes. +Classify git changes as user-facing or workflow-only. Used to skip releases +for infrastructure-only changes. Classification rules are configured in +`pyproject.toml` under `[tool.devx.classify]`. + +```bash +devx ci classify-changes --base origin/master --head HEAD +devx ci classify-changes --base origin/master --head HEAD --github-output +devx ci classify-changes --quiet --check user-facing +devx ci classify-changes --check ansible # custom tag from pyproject.toml +``` + +Options: +- `--base ` — base ref (default: latest tag) +- `--head ` — head ref (default: HEAD) +- `--quiet` — only output true/false +- `--check ` — check specific category: `all` (default), + `user-facing`, or any tag name defined in `[tool.devx.classify.tags]` +- `--github-output` — write results to `$GITHUB_OUTPUT` for CI workflow steps + +Exit code 2 indicates workflow-only changes (no release needed). ### `devx ci detect-release-commit` -Detect whether the latest git commit is a release commit (`release: vX.Y.Z [skip ci]`). +Detect whether the latest git commit is a release commit +(`release: vX.Y.Z [skip ci]`). Writes `is-release=true` or `is-release=false` +to `$GITHUB_OUTPUT` for use in CI workflow conditionals. + +```bash +devx ci detect-release-commit +``` ### `devx ci discover-runners` Discover available Gitea Actions runners for dynamic job distribution. +Queries the Gitea API for registered runners at repository, organization, and +instance (admin) levels. Falls back to `MOLECULE_RUNNERS` repo variable or +`DEFAULT_MAX_RUNNERS` (3). + +```bash +devx ci discover-runners --owner oblachno-oss --repo devx +devx ci discover-runners --owner oblachno-oss --repo devx --count +devx ci discover-runners --owner oblachno-oss --repo devx --indices +``` + +Options: +- `--count` — print the number of available runners +- `--indices` — print a JSON array `[0, 1, ..., N-1]` for use as a dynamic + matrix in Gitea Actions ### `devx ci distribute-files` -Distribute files across parallel runners (round-robin). Used for splitting test suites or workloads across CI runners. +Distribute files across parallel runners (round-robin). Discovers files +matching a glob pattern, sorts them for deterministic ordering, then assigns +them round-robin to `max_runners` groups. The assigned group for +`runner_index` is written to `$GITHUB_ENV`. + +```bash +devx ci distribute-files --pattern "tests/integration/test_*.py" \ + --runner-index 1 --max-runners 3 --github-env +``` + +Options: +- `--pattern ` — glob pattern for files to distribute +- `--runner-index ` — current runner index (0-based) +- `--max-runners ` — total number of runners (default: 3) +- `--github-env` — write file list to `$GITHUB_ENV` +- `--skip-if-excess` — skip if fewer files than runners ### `devx ci doc-coverage` -Check documentation coverage for CLI commands and major modules. +Check documentation coverage for CLI commands and major modules. Parses +Click commands from `cli.py` and checks if each has documentation in +`docs/user/cli-commands.md`. Verifies core modules are documented in +`architecture.md` and CI scripts in `ci-cd-workflow.md`. + +```bash +devx ci doc-coverage +devx ci doc-coverage --docs-dir docs/ --fail-on-missing +``` + +Options: +- `--docs-dir ` — path to the docs directory (default: `docs/`) +- `--fail-on-missing` — exit with non-zero status if any documentation is + missing ### `devx ci integration-guard` -Run pytest with cross-runner failure detection and JUnit XML output. Monitors other runners for failures and aborts early if a critical failure is detected. +Run pytest with cross-runner failure detection and JUnit XML output. If any +other integration-tests matrix runner reports failure, the current pytest +subprocess is killed and this runner exits early with code 1. + +```bash +devx ci integration-guard --junit-output junit-results/runner-1.xml -- test_a.py test_b.py +devx ci integration-guard --junit-output junit-results/runner-1.xml -- -x -v --tb=short test_a.py +``` + +Environment variables: +- `GITEA_URL` — base URL of the Gitea instance +- `REPO_TOKEN` — API token with repo access +- `RUN_ID` — workflow run ID (`GITHUB_RUN_ID`) +- `JOB_NAME` — base job name (`GITHUB_JOB`) +- `MATRIX_INDEX` — current matrix index (runner-index) +- `GITEA_REPOSITORY` — repository in `owner/repo` format ### `devx ci merge-junit` -Merge multiple JUnit XML reports from parallel runners into a single consolidated report. +Merge multiple JUnit XML reports from parallel runners into a single +consolidated report. Exit code is non-zero if any merged test suite reports +failures, making it suitable as a CI gating step after matrix jobs. + +```bash +devx ci merge-junit --pattern "junit-results/runner-*.xml" --output junit-merged.xml +``` ### `devx ci notify-failure` -Create a Gitea issue when a CI workflow fails. +Create a Gitea issue when a CI workflow fails. Uses the tea CLI for issue +creation with a `bug` label if available. + +```bash +devx ci notify-failure --repo oblachno-oss/devx --run-id 123 \ + --workflow ci --commit abc123def456 +devx ci notify-failure --repo oblachno-oss/devx --run-id 123 \ + --workflow post-merge/release --commit abc123def456 --auto-login +``` + +Options: +- `--repo ` — repository (required) +- `--run-id ` — CI run ID (required) +- `--workflow ` — workflow name (required) +- `--commit ` — commit SHA (required) +- `--auto-login` — configure tea CLI login from `REPO_TOKEN` before creating + the issue ### `devx ci post-merge` -Update Vikunja task after a merge to master. +Update Vikunja task after a merge to master. Extracts the task ID from the +commit message, marks the task as done, and posts a comment with the merge SHA. + +```bash +devx ci post-merge "DEVX-12 feat: add feature" --git-sha abc123def456 +``` ### `devx ci pr-review` -Run automated PR review: check architecture compliance, best practices, and quality. +Run automated PR review. Fetches the PR diff via the Gitea API and runs a +series of checks, posting a structured review (`COMMENT` or +`REQUEST_CHANGES`). + +Checks: architecture compliance, best practices, security, i18n, resource +management, documentation, test coverage, and commit conventions. + +```bash +devx ci pr-review 42 oblachno-oss/devx +``` ### `devx ci publish` -Build package, publish to Gitea PyPI registry, and create Gitea release. +Build package, publish to Gitea PyPI registry (or standard PyPI), and create +a Gitea release with git-cliff-generated notes. + +```bash +devx ci publish v1.0.0 oblachno-oss/devx +devx ci publish v1.0.0 oblachno-oss/devx --registry-url https://git.example.com/api/packages/owner/pypi +devx ci publish v1.0.0 oblachno-oss/devx --skip-build # Gitea release only +``` + +Options: +- `--registry-url ` — Gitea PyPI registry URL. Defaults to + `DEVX_PYPI_REGISTRY_URL` env var or a URL derived from `GITEA_API_URL`. + When set, publishes to Gitea PyPI instead of standard PyPI (unless + `PYPI_TOKEN` is also set). +- `--skip-build` — skip package build and PyPI publish (for non-Python repos + that only need a Gitea release) ### `devx ci push-badges` -Generate badge SVG files and push them to the `badges` branch. +Generate badge SVG files and push them to the `badges` branch. Also updates +`README.md` and `docs/index.md` on master with cache-busting +`raw/commit//` URLs. + +```bash +devx ci push-badges +devx ci push-badges --output-dir .badges/ --branch master +devx ci push-badges --no-readme-update # skip README update (local testing) +devx ci push-badges --retries 3 # retry on git push failures +``` + +Options: +- `--output-dir ` — temporary directory for badge files (default: + `.badges/`) +- `--branch ` — branch to sync before generating badges (default: + `master`) +- `--no-readme-update` — skip updating README with cache-busting URLs +- `--retries ` — number of attempts on git push failures (default: 1). + Between attempts, fetches latest master and waits 10s. ### `devx ci release` -Automated release: calculate next version, update files, tag, and push. +Automated release: calculate next version, update files, tag, and push. Uses +git-cliff to determine the next semver version from conventional commits. + +```bash +devx ci release +devx ci release --dry-run # preview without making changes +devx ci release --skip-tests # skip lint and tests (emergency only) +devx ci release --verify # check tag/version/changelog alignment +``` + +Options: +- `--dry-run` — show what would happen without making changes +- `--skip-tests` — skip lint and test verification (NOT recommended — only + for emergency releases) +- `--verify` — verify tag/version/changelog alignment and exit (no changes + made) ### `devx ci sync-wiki` -Sync documentation from `docs/` to the Gitea wiki. +Sync documentation from `docs/` to the Gitea wiki. Reads `docs/mapping.json` +for file-to-page mapping. Pages that exist in the wiki but not in the mapping +are left untouched. + +```bash +devx ci sync-wiki --repo oblachno-oss/devx +devx ci sync-wiki --repo oblachno-oss/devx --dry-run +devx ci sync-wiki --repo oblachno-oss/devx --verify +devx ci sync-wiki --repo oblachno-oss/devx --strict +``` + +Options: +- `--dry-run` — show what would happen without making changes +- `--repo ` — repository (auto-detected if omitted) +- `--verify` — after syncing, verify each page has non-empty content. Exit 1 + if any page is empty or mismatched. +- `--strict` — full integrity check: verify page count, missing pages, stale + pages, and content. Implies `--verify`. ### `devx ci validate-commit-msg` -Validate commit messages for conventional commit format. +Validate commit messages for conventional commit format. On feature branches: +conventional commits only (no `{PREFIX}-N` prefix). On master: must have +`{PREFIX}-N` prefix from auto-merge, followed by a conventional commit +message. + +```bash +devx ci validate-commit-msg commit-msg.txt +devx ci validate-commit-msg commit-msg.txt --branch master +``` + +Options: +- `--branch ` — override branch detection (for CI use) ## Tools Commands ### `devx tools check-test-speed` -Run unit tests and enforce execution-time budgets: -- **Total suite time** must not exceed `--max-seconds` (default: 10s). -- **Per-test time** — no individual test may exceed `--max-single-seconds` (default: 0.5s, 0 to disable). +Run unit tests and enforce execution-time budgets. Two quality gates: + +- **Total suite time** must not exceed `--max-seconds` (default: 10s) +- **Per-test time** — no individual test may exceed `--max-single-seconds` + (default: 0.5s, 0 to disable) + +Runs `make test-unit` with `PYTEST_ADDOPTS=--durations=0` so pytest emits +per-test timing lines. ```bash -python3 -m devx.tools.check_test_speed --max-seconds 10 --max-single-seconds 0.5 +devx tools check-test-speed +devx tools check-test-speed --max-seconds 10 +devx tools check-test-speed --max-seconds 4 --max-single-seconds 0.5 ``` ### `devx tools configure-repo` -Configure repository: branch protection + labels via Gitea API. +Configure repository: branch protection and labels via the Gitea REST API. +Sets up master branch protection (required status checks, block on rejected +reviews, block on outdated branch) and creates standard labels. + +```bash +devx tools configure-repo --repo devx --owner oblachno-oss +``` + +Status check contexts are read from `DEVX_STATUS_CHECKS` (comma-separated) or +default to `CI / quality (pull_request)`. ### `devx tools generate-badges` -Generate self-contained SVG badge files from project metrics. +Generate self-contained SVG badge files from project metrics. Runs +pytest-cov, doc-coverage, lint checks, and version extraction, then writes +SVG files that can be served as static files from the Gitea raw file API. + +Badges generated: `coverage.svg`, `tests.svg`, `docs.svg`, `quality.svg`, +`version.svg`, `python.svg`. + +```bash +devx tools generate-badges +devx tools generate-badges --output-dir .badges/ +``` ### `devx tools generate-cliff-config` -Generate a `cliff.toml` configuration file with the correct task ID prefix. -Eliminates the need to manually duplicate and maintain cliff.toml across -repos that use devx. +Generate a `cliff.toml` configuration file with the correct task ID prefix +preprocessor. Eliminates the need to manually duplicate and maintain +`cliff.toml` across repos that use devx. ```bash -python -m devx.tools.generate_cliff_config --prefix GRM -python -m devx.tools.generate_cliff_config --prefix GRM --force # overwrite existing +devx tools generate-cliff-config --prefix GRM +devx tools generate-cliff-config --prefix GRM --output cliff.toml +devx tools generate-cliff-config --prefix GRM --force # overwrite existing ``` +Options: +- `--prefix ` — task ID prefix (default: `DEVX_TASK_PREFIX` env var + or `DEVX`) +- `--output ` — output file path (default: `cliff.toml`) +- `--force` — overwrite existing file + ### `devx tools install-checkmake` -Install checkmake (Makefile linter) if not already present. +Install checkmake (Makefile linter) if not already present. Tries +`go install` first if Go is available, otherwise downloads the latest +pre-built Linux binary from the official GitHub releases. + +```bash +devx tools install-checkmake +``` ### `devx tools install-tools` -Install CI/CD development tools: actionlint, git-cliff, act_runner, tea. +Install CI/CD development tools that are not Python packages: actionlint, +git-cliff, act_runner, and tea. Each tool is installed to `~/.local/bin` if +not already on PATH. Idempotent: skips tools that are already available. + +```bash +devx tools install-tools # install all +devx tools install-tools --tool actionlint # install one +devx tools install-tools --tool git-cliff --tool tea # install specific +devx tools install-tools --list # list status +``` ### `devx tools setup` -Project setup: install Python deps and pre-commit hooks. +Project setup: install Python dependencies (editable mode with extras), +Ansible Galaxy collections (if `ansible/requirements.yml` exists), pre-commit +hooks (pre-commit, commit-msg, pre-push), and configure the tea CLI login +profile from `.env`. + +```bash +devx tools setup --bin .venv/bin +devx tools setup --bin .venv/bin --extras "ci,lint" +devx tools setup --bin .venv/bin --no-pre-commit --no-tea-login +``` + +Options: +- `--bin ` — virtualenv bin directory (required) +- `--extras ` — pip extras to install (default: `dev`) +- `--no-pre-commit` — skip pre-commit hook installation +- `--no-tea-login` — skip tea CLI login configuration ## Molecule Commands -### `devx molecule distribute` - -Distribute molecule test pairs across parallel runners. - -### `devx molecule discover-runners` - -Discover available Gitea Actions runners for molecule tests. - -### `devx molecule guard` - -Run molecule tests sequentially with CI failure polling. +Molecule commands require the `molecule` extra (`pip install devx[molecule]`). ### `devx molecule all` -Run all molecule scenarios on all supported OS platforms. +Run all molecule scenarios on all supported OS platforms. Sequential +execution — CI uses the parallel matrix instead. + +```bash +devx molecule all +devx molecule all --bin .venv/bin +``` + +### `devx molecule discover-runners` + +Discover available Gitea Actions runners for molecule tests. Same logic as +`devx ci discover-runners` but intended for molecule-specific workflows. + +```bash +devx molecule discover-runners --owner oblachno-oss --repo devx --indices +``` + +### `devx molecule distribute` + +Distribute molecule (scenario, platform) pairs across N parallel runners. +Discovers scenarios under `ansible/roles/*/molecule/` and crosses them with +the supported OS platform matrix. + +```bash +devx molecule distribute --runner-index 1 --max-runners 3 +devx molecule distribute --list # list all scenarios +devx molecule distribute --list-platforms # list platforms +devx molecule distribute --roles-root ansible/roles # multi-role repos +``` + +Options: +- `--runner-index ` — current runner index (0-based) +- `--max-runners ` — total number of runners (default: 3) +- `--list` — list all scenarios, one per line +- `--list-platforms` — list all platforms, one per line +- `--roles-root ` — roles root directory for multi-role repos (default: + `ansible/roles`) + +### `devx molecule guard` + +Run molecule tests sequentially with CI failure polling. A background thread +polls the Gitea API. If any other molecule matrix runner reports failure, the +current molecule subprocess is killed and this runner exits early with code 1. + +```bash +devx molecule guard pair1 pair2 pair3 +devx molecule guard --roles-root ansible/roles pair1 pair2 +devx molecule guard --junit-output junit-results/runner-1.xml pair1 pair2 +``` + +Each pair is encoded as: +- **Single-role (4-part):** `scenario|platform_name|platform_image|platform_command` +- **Multi-role (5-part):** `role|scenario|platform_name|platform_image|platform_command` + +Options: +- `--roles-root ` — roles root directory for multi-role repos +- `--junit-output ` — generate JUnit XML report + +Environment variables: +- `GITEA_URL` — base URL of the Gitea instance +- `REPO_TOKEN` — API token with repo access +- `RUN_ID` — workflow run ID (`GITHUB_RUN_ID`) +- `JOB_NAME` — base job name (`GITHUB_JOB`) +- `MATRIX_INDEX` — current matrix index (runner-index) +- `GITEA_REPOSITORY` — repository in `owner/repo` format