Public Access
Sync wiki from docs/ [skip ci]
+599
@@ -1 +1,600 @@
|
|||||||
# Architecture
|
# Architecture
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
```text
|
||||||
|
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 (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, pl)
|
||||||
|
├── 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
|
||||||
|
│ ├── distribute_files.py # Distribute files across parallel runners
|
||||||
|
│ ├── integration_guard.py # Run pytest with cross-runner fail-fast
|
||||||
|
│ ├── 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
|
||||||
|
│ ├── check_test_isolation.py # Pytest plugin: detect un-hermetic test patterns
|
||||||
|
│ ├── 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
|
||||||
|
|
||||||
|
### `__init__.py`
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
### `cli.py`
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
The CLI is registered as a console script via `pyproject.toml`:
|
||||||
|
```toml
|
||||||
|
[project.scripts]
|
||||||
|
devx = "devx.cli:cli"
|
||||||
|
```
|
||||||
|
|
||||||
|
### `config.py`
|
||||||
|
|
||||||
|
Shared configuration constants for all devx modules. All defaults can be
|
||||||
|
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`)
|
||||||
|
- `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
|
||||||
|
|
||||||
|
### `exceptions.py`
|
||||||
|
|
||||||
|
Custom exception hierarchy:
|
||||||
|
|
||||||
|
- `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).
|
||||||
|
|
||||||
|
### `i18n.py`
|
||||||
|
|
||||||
|
Simple i18n system using a JSON translations file (`translations.json`).
|
||||||
|
Supports six languages: `en`, `bg`, `de`, `pl`, `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, create idempotent)
|
||||||
|
- Actions (list runs, list jobs, get job logs)
|
||||||
|
- Actions variables (get, set idempotent)
|
||||||
|
- 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, 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 <conventional commit>`.
|
||||||
|
|
||||||
|
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 (for example, `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/<sha>/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 `CI_GITEA_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.
|
||||||
|
|
||||||
|
### `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.
|
||||||
|
|
||||||
|
## 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 in the target repo), 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`.
|
||||||
|
|
||||||
|
### `check_test_isolation.py`
|
||||||
|
|
||||||
|
Pytest plugin (auto-discovered via `pytest11` entry point) that
|
||||||
|
statically analyzes test files for un-hermetic patterns causing slow
|
||||||
|
or flaky tests: unpatched `subprocess.run`/`time.sleep` calls, known
|
||||||
|
subprocess-spawning helpers called without `@patch`, and excessive
|
||||||
|
loop iterations (>100). Also available as a standalone CLI for CI
|
||||||
|
gates and pre-commit hooks. See ADR-0001 for design rationale.
|
||||||
|
|
||||||
|
### `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 / validate (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. 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.
|
||||||
|
|
||||||
|
### `molecule/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 (for example, `release.py` imports from
|
||||||
|
`classify_changes.py`)
|
||||||
|
|
||||||
|
## Data flow
|
||||||
|
|
||||||
|
### PR lifecycle
|
||||||
|
|
||||||
|
```text
|
||||||
|
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: <vikunja task title>")
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
CI workflow (ci.yml) triggers:
|
||||||
|
│
|
||||||
|
├── validate (single job: quality + detect-changes +
|
||||||
|
│ release-dry-run + pr-review + pre-merge validation)
|
||||||
|
│ ├── quality steps (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)
|
||||||
|
│ ├── pre-merge validation (check_auto_merge_ready.py)
|
||||||
|
│ └── 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 <conventional commit>" title
|
||||||
|
└── push to master
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
Post-merge workflow triggers (see below)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Post-merge flow
|
||||||
|
|
||||||
|
```text
|
||||||
|
Push to master (squash-merge commit: "DEVX-N <conventional commit>")
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
Post-merge workflow (post-merge.yml) triggers:
|
||||||
|
│
|
||||||
|
├── detect-and-configure (single job)
|
||||||
|
│ ├── configure-repo (configure_repo.py)
|
||||||
|
│ ├── detect-type (detect_release_commit.py)
|
||||||
|
│ │ └── is-release? → skip all steps except badges
|
||||||
|
│ └── validate-commit-msg (validate_commit_msg.py --branch master)
|
||||||
|
│
|
||||||
|
└── release-and-maintain (needs detect-and-configure)
|
||||||
|
├── release (release.py) [skip if release commit or workflow-only]
|
||||||
|
│ ├── 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
|
||||||
|
│ │
|
||||||
|
│ ▼
|
||||||
|
│ publish (publish.py) [if release created a tag]
|
||||||
|
│ ├── 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
|
||||||
|
│
|
||||||
|
├── sync-wiki (sync_wiki.py --strict) [skip if automated]
|
||||||
|
│ └── sync docs/ to Gitea wiki with integrity check
|
||||||
|
│
|
||||||
|
├── vikunja (post_merge.py) [skip if automated]
|
||||||
|
│ ├── extract task ID from commit message
|
||||||
|
│ ├── mark Vikunja task as done
|
||||||
|
│ └── post comment with merge SHA
|
||||||
|
│
|
||||||
|
└── 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
|
||||||
|
```
|
||||||
|
|
||||||
|
### Publish flow
|
||||||
|
|
||||||
|
```text
|
||||||
|
Within release-and-maintain job (after release step creates a tag):
|
||||||
|
│
|
||||||
|
├── install build, twine, git-cliff, tea
|
||||||
|
├── configure tea login
|
||||||
|
├── checkout release tag
|
||||||
|
│
|
||||||
|
└── 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
|
||||||
|
|
||||||
|
```text
|
||||||
|
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/<sha>/ 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` `CI_GITEA_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.
|
||||||
|
|||||||
+581
-1
@@ -1 +1,581 @@
|
|||||||
# CI-CD-Workflow
|
# CI/CD Workflow
|
||||||
|
|
||||||
|
devx uses Gitea Actions for CI/CD automation. Two workflows implement a
|
||||||
|
complete pipeline: pull request validation and post-merge release
|
||||||
|
automation (including publishing).
|
||||||
|
|
||||||
|
## Workflow overview
|
||||||
|
|
||||||
|
```text
|
||||||
|
PR opened/synchronized ──► CI (ci.yml)
|
||||||
|
│ ├── validate (quality + detect-changes +
|
||||||
|
│ │ release-dry-run + pr-review +
|
||||||
|
│ │ pre-merge validation)
|
||||||
|
│ └── auto-merge ──► squash-merge to master
|
||||||
|
│ │
|
||||||
|
▼ ▼
|
||||||
|
Push to master ──► Post-merge (post-merge.yml)
|
||||||
|
├── detect-and-configure (detect-type +
|
||||||
|
│ validate-commit-msg +
|
||||||
|
│ configure-repo)
|
||||||
|
└── release-and-maintain
|
||||||
|
├── release ──► tag vX.Y.Z
|
||||||
|
├── publish ──► Gitea PyPI registry + Gitea release
|
||||||
|
├── sync-wiki
|
||||||
|
├── vikunja
|
||||||
|
└── badges (always runs)
|
||||||
|
```
|
||||||
|
|
||||||
|
## CI workflow (`ci.yml`)
|
||||||
|
|
||||||
|
Runs on pull requests (opened and synchronize) and manual dispatch.
|
||||||
|
|
||||||
|
### Jobs
|
||||||
|
|
||||||
|
#### `validate`
|
||||||
|
|
||||||
|
The single validation job. Consolidates the former `quality`,
|
||||||
|
`detect-changes`, `release-dry-run`, `pr-review`, and `pre-merge-check`
|
||||||
|
jobs into one job to save checkout+setup overhead. Runs on every PR.
|
||||||
|
|
||||||
|
**Quality steps**
|
||||||
|
|
||||||
|
The main quality gate:
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
**`detect-changes` step**
|
||||||
|
|
||||||
|
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 steps.
|
||||||
|
|
||||||
|
**`release-dry-run` step**
|
||||||
|
|
||||||
|
Only runs if the detect-changes step detected user-facing changes. 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`).
|
||||||
|
|
||||||
|
**`pr-review` step**
|
||||||
|
|
||||||
|
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:
|
||||||
|
|
||||||
|
- `COMMENT` — no issues found
|
||||||
|
- `REQUEST_CHANGES` — issues found that must be addressed
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
**Pre-merge validation step**
|
||||||
|
|
||||||
|
Runs on every pull request. Executes
|
||||||
|
`python -m devx.ci.check_auto_merge_ready` with the branch name, PR title,
|
||||||
|
repository, and PR number. Validates auto-merge preconditions before the
|
||||||
|
`auto-merge` job runs:
|
||||||
|
|
||||||
|
1. **Branch name** — must contain a valid task ID (for example,
|
||||||
|
`DEVX-12-fix-foo` → `DEVX-12`)
|
||||||
|
2. **PR title format** — must be `{PREFIX}-N: <vikunja task title>`
|
||||||
|
3. **Vikunja task** — must exist and the title must match the PR title
|
||||||
|
4. **Branch state** — must not be behind master
|
||||||
|
|
||||||
|
#### `auto-merge`
|
||||||
|
|
||||||
|
Depends on `validate`. The final job in the CI workflow. Runs
|
||||||
|
`python -m devx.ci.auto_merge` with the branch name, PR title, repository,
|
||||||
|
and PR number:
|
||||||
|
|
||||||
|
1. **Read task ID** from branch name (for example, `DEVX-12-fix-foo` → `DEVX-12`)
|
||||||
|
2. **Validate PR title format** — must be `{PREFIX}-N: <vikunja task title>`
|
||||||
|
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 <conventional commit message>`
|
||||||
|
6. If the head branch is behind master (HTTP 405), automatically pulls master,
|
||||||
|
rebases, force-pushes, and retries the merge
|
||||||
|
|
||||||
|
The merge commit push to master triggers the post-merge workflow.
|
||||||
|
|
||||||
|
### Smart CI: user-facing vs workflow-only changes
|
||||||
|
|
||||||
|
Not all changes require a new release. The `detect-changes` step in the
|
||||||
|
`validate` job classifies changes using
|
||||||
|
`python -m devx.ci.classify_changes`:
|
||||||
|
|
||||||
|
**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)
|
||||||
|
|
||||||
|
**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
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
## Post-merge workflow (`post-merge.yml`)
|
||||||
|
|
||||||
|
Runs on every push to master. Consolidated into 2 jobs (from 7) to reduce
|
||||||
|
runner overhead: `detect-and-configure` (detect-type + validate-commit-msg +
|
||||||
|
configure-repo) and `release-and-maintain` (release + publish + sync-wiki +
|
||||||
|
badges + vikunja). Individual steps within `release-and-maintain` are
|
||||||
|
conditional on the `detect-and-configure` job's outputs.
|
||||||
|
|
||||||
|
### Job dependency graph
|
||||||
|
|
||||||
|
```text
|
||||||
|
detect-and-configure
|
||||||
|
├── configure-repo (independent, skip if release commit)
|
||||||
|
├── detect-type → is-release? is-automated?
|
||||||
|
└── validate-commit-msg (skip if release commit)
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
release-and-maintain (needs detect-and-configure)
|
||||||
|
├── release (skip if release commit or workflow-only)
|
||||||
|
│ └── publish (if release created a tag)
|
||||||
|
├── sync-wiki (skip if automated)
|
||||||
|
├── vikunja (skip if automated)
|
||||||
|
└── badges (always runs)
|
||||||
|
```
|
||||||
|
|
||||||
|
`sync-wiki` and `vikunja` run only on non-automated commits (that is, real PR
|
||||||
|
merges) so that the wiki and task tracker are only updated when a human
|
||||||
|
change lands. They skip on release commits and automated commits.
|
||||||
|
|
||||||
|
The `badges` step always runs (even on release commits) so badges (tests,
|
||||||
|
coverage, version, etc.) are always current. It runs last so it picks up
|
||||||
|
any version bump the release step created.
|
||||||
|
|
||||||
|
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 steps skip. The `publish` step builds and publishes the
|
||||||
|
package to the Gitea PyPI registry within the same `release-and-maintain`
|
||||||
|
job (it checks out the release tag).
|
||||||
|
|
||||||
|
### Post-merge jobs
|
||||||
|
|
||||||
|
#### `detect-and-configure`
|
||||||
|
|
||||||
|
The first post-merge job. Consolidates the former `detect-type`,
|
||||||
|
`validate-commit-msg`, and `configure-repo` jobs. Outputs `is-release`,
|
||||||
|
`is-automated`, and `user-facing-changed` for the `release-and-maintain`
|
||||||
|
job.
|
||||||
|
|
||||||
|
**`detect-type` step**
|
||||||
|
|
||||||
|
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` (and `is-automated`) to the job output. The
|
||||||
|
`release-and-maintain` job uses these to conditionally skip steps for
|
||||||
|
release commits.
|
||||||
|
|
||||||
|
**`validate-commit-msg` step**
|
||||||
|
|
||||||
|
Skips for release/automated commits. Validates the latest commit message
|
||||||
|
using `python -m devx.ci.validate_commit_msg --branch master`. On master,
|
||||||
|
commits must follow `{PREFIX}-N: <conventional commit>` format (added by
|
||||||
|
auto-merge).
|
||||||
|
|
||||||
|
**`configure-repo` step**
|
||||||
|
|
||||||
|
Ensures branch protection and labels are configured using
|
||||||
|
`python -m devx.tools.configure_repo --repo <name> --owner <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 / validate (pull_request)`
|
||||||
|
|
||||||
|
On failure, the `notify_failure` step creates a Gitea issue.
|
||||||
|
|
||||||
|
#### `release-and-maintain`
|
||||||
|
|
||||||
|
Depends on `detect-and-configure`. The second post-merge job. Consolidates
|
||||||
|
the former `release`, `publish`, `sync-wiki`, `badges`, and `vikunja` jobs.
|
||||||
|
Individual steps are conditional on the `detect-and-configure` job's outputs.
|
||||||
|
|
||||||
|
**`release` step**
|
||||||
|
|
||||||
|
Skips for release commits and workflow-only changes. The core release
|
||||||
|
automation step. 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 (for example,
|
||||||
|
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` step**
|
||||||
|
|
||||||
|
Skips for automated commits. Syncs documentation from `docs/` to the Gitea
|
||||||
|
wiki using `python -m devx.ci.sync_wiki --repo <owner/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` step**
|
||||||
|
|
||||||
|
Always runs (even on 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 step recently 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/<name>.svg` URLs with `raw/commit/<sha>/<name>.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` step**
|
||||||
|
|
||||||
|
Skips for automated commits. Updates the Vikunja task after a merge using
|
||||||
|
`python -m devx.ci.post_merge --git-sha <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.
|
||||||
|
|
||||||
|
**`publish` step**
|
||||||
|
|
||||||
|
Only runs if the `release` step created a tag. Builds and publishes the
|
||||||
|
package within the same `release-and-maintain` job (checks out the release
|
||||||
|
tag). Runs `python -m devx.ci.publish <tag> <owner/repo>`:
|
||||||
|
|
||||||
|
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 `CI_GITEA_TOKEN`
|
||||||
|
4. **Build and publish** — `python -m devx.ci.publish <tag> <owner/repo>`:
|
||||||
|
- Build the package with `python -m build`
|
||||||
|
- Publish to the Gitea PyPI registry (default) using `twine upload
|
||||||
|
--repository-url <url> -u <token> -p <token>`
|
||||||
|
- 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
|
||||||
|
(for example, `DEVX-12-fix-foo` → `DEVX-12`). 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 <conventional commit>` title.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python -m devx.ci.auto_merge <branch> <pr_title> <owner/repo> <pr_number>
|
||||||
|
```
|
||||||
|
|
||||||
|
### `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 <tag> <owner/repo> [--registry-url <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 <pr_number> <owner/repo>
|
||||||
|
```
|
||||||
|
|
||||||
|
### `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 `CI_GITEA_TOKEN`.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python -m devx.ci.notify_failure --repo <owner/repo> --run-id <id> \
|
||||||
|
--workflow <name> --commit <sha> [--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_msg> [--commit-sha <sha>] [--git-sha <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 <ref>] [--head <ref>] \
|
||||||
|
[--quiet] [--check <category>] [--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 <owner> --repo <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/<sha>/` URLs.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python -m devx.ci.push_badges [--output-dir <dir>] [--branch <branch>] \
|
||||||
|
[--no-readme-update] [--retries <n>]
|
||||||
|
```
|
||||||
|
|
||||||
|
### `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 <i> --max-runners <n>
|
||||||
|
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
|
||||||
|
(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 <commit_msg_file> [--branch <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 <owner/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 <file>]...
|
||||||
|
```
|
||||||
|
|
||||||
|
### `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 <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 <glob> --runner-index <i> \
|
||||||
|
--max-runners <n> [--github-env] [--skip-if-excess]
|
||||||
|
```
|
||||||
|
|
||||||
|
### `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 -- <pytest args>
|
||||||
|
```
|
||||||
|
|
||||||
|
## 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 <conventional commit>` title
|
||||||
|
2. **Post-merge triggers** — the merge push triggers `post-merge.yml`
|
||||||
|
3. **detect-and-configure** — detects release commit, validates commit
|
||||||
|
message, and ensures branch protection/labels
|
||||||
|
4. **release** (step in `release-and-maintain`) — `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. **publish** (step in `release-and-maintain`) — `publish.py` builds the
|
||||||
|
package, publishes to the Gitea PyPI registry, and creates a Gitea
|
||||||
|
release with git-cliff notes (checks out the release tag within the
|
||||||
|
same job)
|
||||||
|
6. **sync-wiki** (step in `release-and-maintain`) — documentation is synced
|
||||||
|
to the Gitea wiki
|
||||||
|
7. **vikunja** (step in `release-and-maintain`) — the corresponding Vikunja
|
||||||
|
task is marked as done
|
||||||
|
8. **badges** (step in `release-and-maintain`) — quality badges are
|
||||||
|
regenerated and pushed to the `badges` branch; README and docs/index.md
|
||||||
|
are updated with cache-busting URLs
|
||||||
|
|
||||||
|
The release commit's post-merge run skips all steps except `badges` (which
|
||||||
|
picks up the new version number). This prevents infinite loops.
|
||||||
|
|
||||||
|
## Failure handling
|
||||||
|
|
||||||
|
Every job in the CI and post-merge 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.
|
||||||
|
|||||||
+559
-1
@@ -1 +1,559 @@
|
|||||||
# CLI-Commands
|
# 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. Reads the task ID from the branch
|
||||||
|
name, validates the PR title format against
|
||||||
|
the Vikunja task title, extracts the conventional commit message from PR
|
||||||
|
commits, and squash-merges with `{PREFIX}-N <conventional commit>` 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 <branch> <pr_title> <owner/repo> <pr_number>
|
||||||
|
# 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. 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 six
|
||||||
|
supported languages (en, bg, de, ru, zh, pl)
|
||||||
|
|
||||||
|
```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. 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 <ref>` — base ref (default: latest tag)
|
||||||
|
- `--head <ref>` — head ref (default: HEAD)
|
||||||
|
- `--quiet` — only output true/false
|
||||||
|
- `--check <category>` — 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]`). 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). 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>` — glob pattern for files to distribute
|
||||||
|
- `--runner-index <i>` — current runner index (0-based)
|
||||||
|
- `--max-runners <n>` — 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. 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/ --source-dir src/ --fail-on-missing
|
||||||
|
```
|
||||||
|
|
||||||
|
Options:
|
||||||
|
- `--docs-dir <dir>` — path to the docs directory (default: `docs/`)
|
||||||
|
- `--source-dir <dir>` — path to the source directory (default: auto-detect)
|
||||||
|
- `--fail-on-missing` — exit with non-zero status if any documentation is
|
||||||
|
missing
|
||||||
|
|
||||||
|
### `devx ci lint-docs`
|
||||||
|
|
||||||
|
Lint documentation files for structure, broken links, heading hierarchy,
|
||||||
|
duplicate headings, TODO/FIXME markers, and trailing whitespace.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
devx ci lint-docs
|
||||||
|
devx ci lint-docs --root . --fix
|
||||||
|
devx ci lint-docs --no-check-links --no-check-stale
|
||||||
|
```
|
||||||
|
|
||||||
|
Options:
|
||||||
|
- `--root <dir>` — repository root directory (default: `.`)
|
||||||
|
- `--docs-dir <dir>` — docs directory (default: `<root>/docs`)
|
||||||
|
- `--check-links/--no-check-links` — check internal links (default: yes)
|
||||||
|
- `--check-headings/--no-check-headings` — check heading hierarchy (default: yes)
|
||||||
|
- `--check-todo/--no-check-todo` — check for TODO/FIXME markers (default: yes)
|
||||||
|
- `--check-stale/--no-check-stale` — check for stale docs (default: no)
|
||||||
|
- `--check-trailing/--no-check-trailing` — check trailing whitespace (default: yes)
|
||||||
|
- `--check-duplicates/--no-check-duplicates` — check duplicate headings (default: yes)
|
||||||
|
- `--fix` — auto-fix trailing whitespace
|
||||||
|
|
||||||
|
### `devx ci integration-guard`
|
||||||
|
|
||||||
|
Run 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 with code 1.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
devx ci integration-guard -- test_a.py test_b.py
|
||||||
|
devx ci integration-guard -- -x -v --tb=short test_a.py
|
||||||
|
```
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
### `devx ci notify-failure`
|
||||||
|
|
||||||
|
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 <owner/repo>` — repository (required)
|
||||||
|
- `--run-id <id>` — CI run ID (required)
|
||||||
|
- `--workflow <name>` — workflow name (required)
|
||||||
|
- `--commit <sha>` — commit SHA (required)
|
||||||
|
- `--auto-login` — configure tea CLI login from `CI_GITEA_TOKEN` before creating
|
||||||
|
the issue
|
||||||
|
|
||||||
|
### `devx ci post-merge`
|
||||||
|
|
||||||
|
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. 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 (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 <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. Also updates
|
||||||
|
`README.md` and `docs/index.md` on master with cache-busting
|
||||||
|
`raw/commit/<sha>/` 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 <dir>` — temporary directory for badge files (default:
|
||||||
|
`.badges/`)
|
||||||
|
- `--branch <branch>` — branch to sync before generating badges (default:
|
||||||
|
`master`)
|
||||||
|
- `--no-readme-update` — skip updating README with cache-busting URLs
|
||||||
|
- `--retries <n>` — 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. 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. 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 <owner/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. 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 <branch>` — override branch detection (for CI use)
|
||||||
|
|
||||||
|
## Tools Commands
|
||||||
|
|
||||||
|
### `devx tools check-test-speed`
|
||||||
|
|
||||||
|
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
|
||||||
|
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 check-test-isolation`
|
||||||
|
|
||||||
|
Statically analyze test files for un-hermetic patterns that cause slow
|
||||||
|
or flaky tests. Also available as a **pytest plugin** (auto-discovered
|
||||||
|
via the `pytest11` entry point when devx is installed — runs
|
||||||
|
automatically on every `pytest` invocation and **fails on violations**).
|
||||||
|
|
||||||
|
Detected patterns (hard errors — exit non-zero):
|
||||||
|
|
||||||
|
- **unpatched-subprocess**: `subprocess.run/call/Popen/check_call/check_output`
|
||||||
|
called in a test function without `@patch` or `with patch(...)`
|
||||||
|
- **unpatched-sleep**: `time.sleep` called without `@patch`
|
||||||
|
- **unpatched-helper**: known subprocess-spawning helpers (`update_doc_versions`,
|
||||||
|
`run_cmd`, `run_tests`) called without `@patch` or patching their internal deps
|
||||||
|
- **excessive-iterations**: `for _ in range(N)` where N > 100
|
||||||
|
- **heavy-module-import**: `httpx`, `ansible`, etc. imported at module level
|
||||||
|
- **reload-without-cleanup**: `importlib.reload()` called an odd number of times
|
||||||
|
|
||||||
|
Advisory patterns (exit 0 — runtime audit is authoritative):
|
||||||
|
|
||||||
|
- **transitive-subprocess**: `CliRunner.invoke(target)` where `target`
|
||||||
|
transitively calls `subprocess.run` without being patched. Detected via
|
||||||
|
static call-graph analysis. The runtime subprocess audit catches actual
|
||||||
|
leaks — if a real subprocess runs without `@patch`, the test fails.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
devx tools check-test-isolation
|
||||||
|
devx tools check-test-isolation --test-path tests/
|
||||||
|
devx tools check-test-isolation --categories unpatched-subprocess,transitive-subprocess
|
||||||
|
devx tools check-test-isolation --max-loop-iterations 50
|
||||||
|
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
|
||||||
|
- `--test-isolation-max-loop N` — max iterations per loop (default: 100)
|
||||||
|
|
||||||
|
### `devx tools configure-repo`
|
||||||
|
|
||||||
|
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. 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
|
||||||
|
preprocessor. Eliminates the need to manually duplicate and maintain
|
||||||
|
`cliff.toml` across repos that use devx.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
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 <prefix>` — task ID prefix (default: `DEVX_TASK_PREFIX` env var
|
||||||
|
or `DEVX`)
|
||||||
|
- `--output <file>` — output file path (default: `cliff.toml`)
|
||||||
|
- `--force` — overwrite existing file
|
||||||
|
|
||||||
|
### `devx tools install-checkmake`
|
||||||
|
|
||||||
|
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 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 dependencies (editable mode with extras),
|
||||||
|
Ansible Galaxy collections (if `ansible/requirements.yml` exists in the target repo), 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 <dir>` — virtualenv bin directory (required)
|
||||||
|
- `--extras <groups>` — pip extras to install (default: `dev`)
|
||||||
|
- `--no-pre-commit` — skip pre-commit hook installation
|
||||||
|
- `--no-tea-login` — skip tea CLI login configuration
|
||||||
|
|
||||||
|
### `devx tools rebase`
|
||||||
|
|
||||||
|
Rebase the current branch onto `origin/master` and force-push with
|
||||||
|
`--force-with-lease`. Checks if the branch is behind master first —
|
||||||
|
if up-to-date, exits without doing anything.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
devx tools rebase # rebase + force-push
|
||||||
|
devx tools rebase -- --no-push # rebase locally only
|
||||||
|
```
|
||||||
|
|
||||||
|
Options (pass after `--`):
|
||||||
|
- `--no-push` — rebase locally without pushing
|
||||||
|
|
||||||
|
### `devx tools pr-rebase`
|
||||||
|
|
||||||
|
Rebase a pull request's head branch onto master via the Gitea API
|
||||||
|
(server-side). This triggers a new `pull_request synchronize` event,
|
||||||
|
which starts a new CI run. Useful when you don't have the branch
|
||||||
|
checked out locally.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
devx tools pr-rebase -- --pr 42 # rebase PR #42
|
||||||
|
devx tools pr-rebase # auto-detect PR from current branch
|
||||||
|
```
|
||||||
|
|
||||||
|
Options (pass after `--`):
|
||||||
|
- `--pr <N>` — PR number (auto-detected from current branch if omitted)
|
||||||
|
|
||||||
|
## Molecule Commands
|
||||||
|
|
||||||
|
Molecule commands require the `molecule` extra (`pip install devx[molecule]`).
|
||||||
|
|
||||||
|
### `devx molecule all`
|
||||||
|
|
||||||
|
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 <i>` — current runner index (0-based)
|
||||||
|
- `--max-runners <n>` — total number of runners (default: 3)
|
||||||
|
- `--list` — list all scenarios, one per line
|
||||||
|
- `--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
|
||||||
|
|||||||
+161
-1
@@ -1 +1,161 @@
|
|||||||
# Getting-Started
|
# Getting Started with devx
|
||||||
|
|
||||||
|
This guide walks you through installing devx, configuring it for your project,
|
||||||
|
and setting up a complete CI/CD pipeline.
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
- **Python 3.12+**
|
||||||
|
- **A Gitea instance** with Actions enabled
|
||||||
|
- **A Gitea API token** with repo, workflow, and organization scopes
|
||||||
|
- **(Optional) Vikunja API token** for task tracking integration
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
devx is published to the Gitea PyPI registry. Configure pip to use it:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Configure Gitea PyPI registry
|
||||||
|
pip config set global.extra-index-url https://git.oblachno.oblachno.fyi/api/packages/oblachno-oss/pypi/simple
|
||||||
|
|
||||||
|
# Install devx
|
||||||
|
pip install devx
|
||||||
|
```
|
||||||
|
|
||||||
|
Or install from source:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone https://git.oblachno.oblachno.fyi/oblachno-oss/devx.git
|
||||||
|
cd devx
|
||||||
|
make setup
|
||||||
|
```
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
### 1. Configure environment variables
|
||||||
|
|
||||||
|
Create a `.env` file in your project root:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
CI_GITEA_TOKEN=your_gitea_api_token
|
||||||
|
VIKUNJA_TOKEN=your_vikunja_api_token # optional
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Add devx to your project
|
||||||
|
|
||||||
|
Add devx to your `pyproject.toml`:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[project]
|
||||||
|
dependencies = [
|
||||||
|
"devx>=0.47.5",
|
||||||
|
]
|
||||||
|
|
||||||
|
[project.optional-dependencies]
|
||||||
|
dev = [
|
||||||
|
"devx>=0.47.5",
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Set up the Makefile
|
||||||
|
|
||||||
|
devx provides a shared Makefile fragment. Add this to your `Makefile`:
|
||||||
|
|
||||||
|
```makefile
|
||||||
|
include devx.mak
|
||||||
|
```
|
||||||
|
|
||||||
|
Run `devx tools setup` to install all development tools (actionlint, git-cliff,
|
||||||
|
tea CLI, etc.) and configure pre-commit hooks.
|
||||||
|
|
||||||
|
### 4. Create the docs structure
|
||||||
|
|
||||||
|
devx expects a `docs/` directory with at minimum:
|
||||||
|
|
||||||
|
```text
|
||||||
|
docs/
|
||||||
|
├── index.md # Documentation home page
|
||||||
|
├── mapping.json # Wiki page title mappings
|
||||||
|
├── user/ # User-facing documentation
|
||||||
|
│ └── cli-commands.md
|
||||||
|
└── tech/ # Technical documentation
|
||||||
|
├── architecture.md
|
||||||
|
└── ci-cd-workflow.md
|
||||||
|
```
|
||||||
|
|
||||||
|
Example `docs/mapping.json`:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"index.md": "Home",
|
||||||
|
"user/cli-commands.md": "CLI-Commands",
|
||||||
|
"tech/architecture.md": "Architecture",
|
||||||
|
"tech/ci-cd-workflow.md": "CI-CD-Workflow"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. Set up CI workflows
|
||||||
|
|
||||||
|
Create `.gitea/workflows/ci.yml` and `.gitea/workflows/post-merge.yml` in your
|
||||||
|
project. See the [CI/CD Workflow guide](ci-cd-workflow) for details.
|
||||||
|
|
||||||
|
### 6. Configure release settings
|
||||||
|
|
||||||
|
Add a `cliff.toml` for git-cliff-based versioning:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
devx tools generate-cliff-config
|
||||||
|
```
|
||||||
|
|
||||||
|
Add `[tool.devx]` section to `pyproject.toml` for project-specific config:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[tool.devx]
|
||||||
|
# Vikunja project ID for task tracking
|
||||||
|
vikunja_project_id = 6
|
||||||
|
|
||||||
|
[tool.devx.classify]
|
||||||
|
# File patterns that are infrastructure (no release needed)
|
||||||
|
infrastructure = [
|
||||||
|
".gitea/**",
|
||||||
|
"docs/**",
|
||||||
|
"tests/**",
|
||||||
|
"AGENTS.md",
|
||||||
|
"README.md",
|
||||||
|
"CHANGELOG.md",
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
## Available Tools
|
||||||
|
|
||||||
|
### CI/CD Automation (`devx.ci.*`)
|
||||||
|
|
||||||
|
- `devx.ci.release` — Automated semver versioning and tagging
|
||||||
|
- `devx.ci.publish` — Package publishing to Gitea PyPI registry
|
||||||
|
- `devx.ci.auto_merge` — Squash-merge automation with task ID validation
|
||||||
|
- `devx.ci.pr_review` — Automated PR review with inline comments
|
||||||
|
- `devx.ci.classify_changes` — User-facing vs workflow-only change detection
|
||||||
|
- `devx.ci.sync_wiki` — Push docs/ to Gitea wiki
|
||||||
|
- `devx.ci.doc_coverage` — Documentation coverage checker
|
||||||
|
- `devx.ci.lint_docs` — Documentation linter (structure, links, headings)
|
||||||
|
- `devx.ci.check_translations` — i18n translation completeness checker
|
||||||
|
- `devx.ci.notify_failure` — Create Gitea issues on CI failures
|
||||||
|
- `devx.ci.distribute_files` — Parallel test file distribution
|
||||||
|
- `devx.ci.distribute_items` — Parallel item distribution across runners
|
||||||
|
- `devx.ci.discover_runners` — Dynamic runner discovery via Gitea API
|
||||||
|
|
||||||
|
### Development Tools (`devx.tools.*`)
|
||||||
|
|
||||||
|
- `devx.tools.setup` — Environment setup (venv, deps, hooks, tools)
|
||||||
|
- `devx.tools.install_tools` — Install CI/CD tools (actionlint, git-cliff, tea)
|
||||||
|
- `devx.tools.create_task` — Create Vikunja tasks
|
||||||
|
- `devx.tools.create_pr` — Create Gitea PRs with task ID in title
|
||||||
|
- `devx.tools.configure_repo` — Configure branch protection and labels
|
||||||
|
- `devx.tools.generate_badges` — Generate quality badge SVGs
|
||||||
|
- `devx.tools.check_test_speed` — Enforce test execution speed limits
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
- Read the [CLI Commands reference](cli-commands) for all available commands
|
||||||
|
- Read the [Architecture guide](architecture) to understand internals
|
||||||
|
- Read the [CI/CD Workflow guide](ci-cd-workflow) for pipeline details
|
||||||
|
|||||||
+175
@@ -0,0 +1,175 @@
|
|||||||
|
# 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, 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).
|
||||||
|
|
||||||
|
[](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/actions)
|
||||||
|
[](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/src/branch/master/LICENSE)
|
||||||
|
[](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/actions)
|
||||||
|
[](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/actions)
|
||||||
|
[](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/wiki)
|
||||||
|
[](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/actions)
|
||||||
|
[](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/releases)
|
||||||
|
[](https://www.python.org/downloads/)
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
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,
|
||||||
|
Chinese, and Polish; projects can extend with their own keys.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
devx is published to the Gitea PyPI registry at
|
||||||
|
`https://git.oblachno.oblachno.fyi/api/packages/oblachno-oss/pypi/simple`.
|
||||||
|
The registry is publicly readable — no authentication required to install.
|
||||||
|
|
||||||
|
### Quick install (one-off)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pip install devx --index-url https://git.oblachno.oblachno.fyi/api/packages/oblachno-oss/pypi/simple
|
||||||
|
```
|
||||||
|
|
||||||
|
### Persistent configuration (recommended)
|
||||||
|
|
||||||
|
Add the registry to `~/.pip/pip.conf`:
|
||||||
|
|
||||||
|
```ini
|
||||||
|
[global]
|
||||||
|
extra-index-url = https://git.oblachno.oblachno.fyi/api/packages/oblachno-oss/pypi/simple
|
||||||
|
```
|
||||||
|
|
||||||
|
Then `pip install devx` works without specifying `--index-url`.
|
||||||
|
|
||||||
|
### As a dependency in another project
|
||||||
|
|
||||||
|
Add devx to your `pyproject.toml` dependencies and configure the registry:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[project]
|
||||||
|
dependencies = [
|
||||||
|
"devx>=0.47.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.47.5"` or `"devx>=0.47.5,<0.48"`.
|
||||||
|
|
||||||
|
### Optional extras
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pip install "devx[ci,lint]" # CI runners and linting (pytest, ruff, pyright, bandit, build, twine)
|
||||||
|
pip install "devx[molecule]" # Molecule testing for Ansible projects
|
||||||
|
pip install "devx[dev]" # Full local development (ci + lint + build + twine)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
devx is a self-contained Python package under `src/devx/`:
|
||||||
|
|
||||||
|
- **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, 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 <command>` — CI/CD automation (17 commands)
|
||||||
|
- `devx tools <command>` — Developer tools (9 commands)
|
||||||
|
- `devx molecule <command>` — 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, pl) |
|
||||||
|
| `CI_GITEA_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
|
||||||
|
- [Getting Started](Getting-Started) — Installation, configuration, and quick start guide
|
||||||
|
- [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
|
||||||
|
|||||||
Reference in New Issue
Block a user