feat: sync missing features from v0.49.x line to master

The v0.49.x tag line diverged from origin/master, leaving many
features only accessible via tags but not on the master branch.

New modules:
- ci/cancel_superseded_runs.py — cancel superseded CI runs
- ci/check_workflow_artifact_deps.py — validate artifact deps
- ci/check_workflow_tofu_init.py — validate tofu init steps
- tools/check_alert_rules.py — validate Prometheus alert rules
- tools/check_ansible_set_fact_to_json.py — lint set_fact usage
- tools/check_docker_init.py — validate Docker init scripts
- utils/jinja.py — Jinja2 template utilities
- utils/ui.py — UI/console utilities

Modified modules:
- distribute_molecule.py: add --include-roles/--exclude-roles
- utils/api.py: add container.credentials for private registry auth
- install_tools.py: retry ansible-galaxy on transient timeouts
- setup_image.py: skip dep resolution with --no-deps
- cli.py: register new commands
- i18n.py: add new translation keys

Also removes accidentally committed .vale/styles/Google/ files.

Test results: 2195 passed, 100% coverage.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
emil
2026-08-09 03:04:39 +02:00
co-authored by Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
parent bd4530094e
commit 5cacbdec09
85 changed files with 4828 additions and 551 deletions
+12 -12
View File
@@ -8,16 +8,16 @@ 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).
> An open source project from **Oblachno** (облачно means *cloudy* in Bulgarian).
[![CI](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/actions/workflows/ci.yml/badge.svg)](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/actions)
[![License: GPL-3.0](https://img.shields.io/badge/license-GPL--3.0-blue)](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/src/branch/master/LICENSE)
[![Coverage](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/raw/commit/3ae96e9fe24eb247be074f92e4a9153033dc1ed7/coverage.svg)](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/actions)
[![Tests](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/raw/commit/3ae96e9fe24eb247be074f92e4a9153033dc1ed7/tests.svg)](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/actions)
[![Docs](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/raw/commit/3ae96e9fe24eb247be074f92e4a9153033dc1ed7/docs.svg)](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/wiki)
[![Code Quality](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/raw/commit/3ae96e9fe24eb247be074f92e4a9153033dc1ed7/quality.svg)](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/actions)
[![Version](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/raw/commit/3ae96e9fe24eb247be074f92e4a9153033dc1ed7/version.svg)](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/releases)
[![Python](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/raw/commit/3ae96e9fe24eb247be074f92e4a9153033dc1ed7/python.svg)](https://www.python.org/downloads/)
[![Coverage](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/raw/commit/8c02351c3190af51b1dfe545a7abbec665c51559/coverage.svg)](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/actions)
[![Tests](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/raw/commit/8c02351c3190af51b1dfe545a7abbec665c51559/tests.svg)](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/actions)
[![Docs](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/raw/commit/8c02351c3190af51b1dfe545a7abbec665c51559/docs.svg)](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/wiki)
[![Code Quality](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/raw/commit/8c02351c3190af51b1dfe545a7abbec665c51559/quality.svg)](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/actions)
[![Version](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/raw/commit/8c02351c3190af51b1dfe545a7abbec665c51559/version.svg)](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/releases)
[![Python](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/raw/commit/8c02351c3190af51b1dfe545a7abbec665c51559/python.svg)](https://www.python.org/downloads/)
## Overview
@@ -74,14 +74,14 @@ Add devx to your `pyproject.toml` dependencies and configure the registry:
```toml
[project]
dependencies = [
"devx>=0.48.1",
"devx>=0.49.5",
]
[tool.pip]
extra-index-url = "https://git.oblachno.oblachno.fyi/api/packages/oblachno-oss/pypi/simple"
```
Pin a specific version if needed: `"devx==0.48.1"` or `"devx>=0.48.1,<0.49"`.
Pin a specific version if needed: `"devx==0.49.5"` or `"devx>=0.49.5,<0.50"`.
### Optional extras
@@ -104,8 +104,8 @@ devx is a self-contained Python package under `src/devx/`:
- **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_all, discover_runners, start_docker,
platforms
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.
@@ -132,7 +132,7 @@ devx provides a `devx` CLI with three command groups:
- `devx ci <command>` — CI/CD automation (17 commands)
- `devx tools <command>` — Developer tools (9 commands)
- `devx molecule <command>` — Molecule testing (3 commands, optional)
- `devx molecule <command>` — Molecule testing (4 commands, optional)
See [CLI Commands](CLI-Commands) for full command documentation with examples.
@@ -132,7 +132,7 @@ unblocked auto-merge across all three repos.
### 2. Double-Prefix Detection (MEDIUM impact)
`check_auto_merge_ready.py` now detects and rejects Vikunja task titles
that include the identifier prefix (for example, "DEVX-127: Fix...").
that include the identifier prefix (for example, "DEVX-127: Fix").
The validator adds the prefix automatically, so a double prefix would
fail validation.
+17 -9
View File
@@ -50,6 +50,7 @@ src/devx/
├── __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
@@ -86,11 +87,11 @@ overridden via environment variables with the `DEVX_` prefix. Provides:
- `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 (for example, `DEVX-N`)
- `TASK_PREFIX` / `TASK_ID_RE` — task ID prefix and regular expression (for example, `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
- `CONVENTIONAL_RE` — conventional commit format regular expression
### `exceptions.py`
@@ -108,7 +109,7 @@ 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
built-in translations, allowing projects to override, or add keys without
modifying the package.
### `api_clients.py`
@@ -170,7 +171,7 @@ from `devx.api_clients`, `devx.config`, `devx.gitea_cli`, and `devx.i18n`.
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
`__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.
@@ -287,7 +288,7 @@ Click commands from `cli.py` and verifies each has documentation in
### `discover_runners.py`
Discovers available Gitea Actions runners at three levels: repository,
organization, and instance (admin). Falls back to the `MOLECULE_RUNNERS` repo
organization, and instance (administrator). 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.
@@ -299,9 +300,9 @@ Distributes files matching a glob pattern across N parallel runners
### `integration_guard.py`
Runs pytest with cross-runner failure detection. A background thread polls
the Gitea API. If any other integration-tests matrix runner reports failure,
the current pytest subprocess is killed and this runner exits early.
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.
## Developer tools (`devx.tools`)
@@ -329,7 +330,7 @@ Supports `--tool` to install specific tools and `--list` to show status.
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`.
off). Runs `make test-unit` with `PYTEST_ADDOPTS=--durations=0`.
### `check_test_isolation.py`
@@ -381,6 +382,13 @@ 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. 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.
+9
View File
@@ -478,6 +478,15 @@ 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.
```bash
python -m devx.molecule.molecule_ci_guard [--roles-root <dir>] pair1 pair2 ...
```
### `validate_commit_msg.py`
Validates commit messages. On feature branches: conventional commits only
+130 -4
View File
@@ -85,7 +85,7 @@ devx ci detect-release-commit
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
instance (administrator) levels. Falls back to `MOLECULE_RUNNERS` repo variable or
`DEFAULT_MAX_RUNNERS` (3).
```bash
@@ -315,6 +315,56 @@ devx ci validate-commit-msg commit-msg.txt --branch master
Options:
- `--branch <branch>` — override branch detection (for CI use)
### `devx ci cancel-superseded-runs`
Cancel in-flight CI runs for the same PR branch when a new push triggers
a new run. Uses the Gitea Actions API to list running pull_request runs
and cancel those with a lower run ID on the same branch.
```bash
devx ci cancel-superseded-runs \
--repo "$REPOSITORY" \
--current-run-id "$GITHUB_RUN_ID" \
--head-branch "$HEAD_REF"
```
Options:
- `--repo <owner/repo>` — repository (required)
- `--current-run-id <id>` — current run ID, not cancelled (required)
- `--head-branch <branch>` — PR head branch name (required)
- `--dry-run` — list superseded runs without cancelling
- `--base-url <url>` — Gitea base URL (default: `GITEA_API_URL` env var)
### `devx ci check-workflow-artifact-deps`
Verify that workflow jobs downloading artifacts depend on the uploading
job. Prevents the class of bug where a download job runs in parallel
with the upload job and fails because the artifact isn't available yet.
```bash
devx ci check-workflow-artifact-deps
devx ci check-workflow-artifact-deps --workflow .gitea/workflows/ci.yml
```
Options:
- `--workflow <path>` — check a specific workflow file
- `--workflows-dir <path>` — override workflows directory
### `devx ci check-workflow-tofu-init`
Verify that workflow jobs using tofu state (tofu output/plan/apply or
scripts that call them) have a tofu-init step in the same job.
```bash
devx ci check-workflow-tofu-init
devx ci check-workflow-tofu-init --workflow .gitea/workflows/deploy.yml
```
Options:
- `--workflow <path>` — check a specific workflow file
- `--workflows-dir <path>` — override workflows directory
- `--state-script <name>` — add a script that uses tofu state (repeatable)
## Tools Commands
### `devx tools check-test-speed`
@@ -323,7 +373,7 @@ 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)
(default: 0.5s, 0 to turn off)
Runs `make test-unit` with `PYTEST_ADDOPTS=--durations=0` so pytest emits
per-test timing lines.
@@ -369,7 +419,7 @@ devx tools check-test-isolation --src-dir src/
Pytest plugin options (automatic when devx is installed):
- `--no-test-isolation`disable static analysis and runtime subprocess audit
- `--no-test-isolation`turn off static analysis and runtime subprocess audit
- `--test-isolation-max-loop N` — max iterations per loop (default: 100)
### `devx tools configure-repo`
@@ -414,7 +464,7 @@ devx tools generate-cliff-config --prefix GRM --force # overwrite existing
Options:
- `--prefix <prefix>` — task ID prefix (default: `DEVX_TASK_PREFIX` env var
or `DEVX`)
- `--output <file>` — output file path (default: `cliff.toml`)
- `--output <file>` — output path (default: `cliff.toml`)
- `--force` — overwrite existing file
### `devx tools install-checkmake`
@@ -488,6 +538,56 @@ devx tools pr-rebase # auto-detect PR from current branch
Options (pass after `--`):
- `--pr <N>` — PR number (auto-detected from current branch if omitted)
### `devx tools check-docker-init`
Check that Docker Compose services with healthchecks have `init: true`.
Without `init: true`, CMD-SHELL healthchecks spawn child processes that
become zombies when PID 1 doesn't reap them.
```bash
devx tools check-docker-init
devx tools check-docker-init --path path/to/docker-compose.yml.j2
```
Options:
- `--path <path>` — check a specific file or directory
- `--templates-dir <path>` — override templates directory (default: `ansible/roles/`)
### `devx tools check-ansible-set-fact-to-json`
Check that Ansible `set_fact` tasks don't misuse `| to_json`. Using
`to_json` in `set_fact` converts native Python types to JSON strings,
causing iteration bugs (for example, iterating over characters instead
of list items).
```bash
devx tools check-ansible-set-fact-to-json
devx tools check-ansible-set-fact-to-json --path path/to/playbook.yml
```
Options:
- `--path <path>` — check a specific file or directory
- `--ansible-dir <path>` — override ansible directories (repeatable)
### `devx tools check-alert-rules`
Validate rendered Prometheus alert rules with `promtool check rules`.
Renders a Jinja2 template with test values and validates the output.
Skips (exits 0) if promtool is not on PATH.
```bash
devx tools check-alert-rules \
--template-path ansible/roles/observability/templates
devx tools check-alert-rules \
--template-path ansible/roles/observability/templates \
--var grafana_base_url=https://grafana.example.com
```
Options:
- `--template-path <path>` — path to templates directory (required)
- `--template-name <name>` — template filename (default: `alert-rules.yml.j2`)
- `--var key=value` — template variables (repeatable)
## Molecule Commands
Molecule commands require the `molecule` extra (`pip install devx[molecule]`).
@@ -531,3 +631,29 @@ Options:
- `--list-platforms` — list all platforms, one per line
- `--roles-root <dir>` — 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
```
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 <dir>` — roles root directory for multi-role repos
Environment variables:
- `GITEA_URL` — base URL of the Gitea instance
- `CI_GITEA_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
+2 -2
View File
@@ -48,12 +48,12 @@ Add devx to your `pyproject.toml`:
```toml
[project]
dependencies = [
"devx>=0.48.1",
"devx>=0.49.5",
]
[project.optional-dependencies]
dev = [
"devx>=0.48.1",
"devx>=0.49.5",
]
```