Compare commits

...
8 Commits
Author SHA1 Message Date
devx-ci-bot 9e604ea2c7 release: v0.26.3 [skip ci] 2026-06-28 14:55:48 +00:00
emil 4bb50bed58 DEVX-97: fix: pin all dependencies to exact versions for reproducibility
Post-merge / detect-type (push) Successful in 10s
Post-merge / validate-commit-msg (push) Successful in 12s
Post-merge / sync-wiki (push) Successful in 24s
Post-merge / vikunja (push) Successful in 15s
Post-merge / release (push) Successful in 32s
Post-merge / configure-repo (push) Successful in 13s
Post-merge / badges (push) Successful in 49s
Post-merge / publish (push) Successful in 24s
Build Images / detect-type (push) Waiting to run
Build Images / build-and-push (push) Blocked by required conditions
Build Images / cleanup (push) Blocked by required conditions
2026-06-28 14:55:03 +00:00
gitea-actions-bot 5bb9dce530 chore: update badge URLs to commit c04f826e [skip ci] 2026-06-28 14:42:36 +00:00
emil 73662a3bf0 DEVX-95: chore: pin all dependency versions to concrete releases
Post-merge / detect-type (push) Successful in 18s
Post-merge / validate-commit-msg (push) Successful in 10s
Post-merge / release (push) Successful in 15s
Post-merge / sync-wiki (push) Successful in 24s
Post-merge / vikunja (push) Successful in 14s
Build Images / detect-type (push) Successful in 45s
Post-merge / publish (push) Has been skipped
Post-merge / configure-repo (push) Successful in 12s
Post-merge / badges (push) Successful in 34s
Build Images / build-and-push (push) Successful in 3m47s
Build Images / cleanup (push) Successful in 2m11s
2026-06-28 14:41:27 +00:00
gitea-actions-bot a1e87b1905 chore: update badge URLs to commit 991d923a [skip ci] 2026-06-28 14:38:20 +00:00
devx-ci-bot d8d0ad04a2 release: v0.26.2 [skip ci] 2026-06-28 14:38:13 +00:00
emil e836c09088 DEVX-96: fix: block admin merge override and auto-approve with review token
Post-merge / detect-type (push) Successful in 9s
Post-merge / validate-commit-msg (push) Successful in 8s
Post-merge / configure-repo (push) Successful in 10s
Post-merge / sync-wiki (push) Successful in 18s
Post-merge / vikunja (push) Successful in 20s
Post-merge / release (push) Successful in 25s
Post-merge / badges (push) Successful in 29s
Post-merge / publish (push) Successful in 16s
Build Images / build-and-push (push) Waiting to run
Build Images / cleanup (push) Blocked by required conditions
Build Images / detect-type (push) Successful in 45s
2026-06-28 14:37:36 +00:00
gitea-actions-bot 507436b134 chore: update badge URLs to commit 8dbdd563 [skip ci] 2026-06-28 12:15:13 +00:00
10 changed files with 85 additions and 40 deletions
+15
View File
@@ -160,6 +160,21 @@ jobs:
token: ${{ secrets.CI_GITEA_TOKEN }} token: ${{ secrets.CI_GITEA_TOKEN }}
- name: Set up environment - name: Set up environment
run: make setup-image run: make setup-image
- name: Post approval review
env:
CI_GITEA_TOKEN: ${{ secrets.REVIEW_GITEA_TOKEN }}
PR_NUMBER: ${{ github.event.number }}
REPOSITORY: ${{ github.repository }}
PYTHONPATH: src
run: |
. .venv/bin/activate
python3 -m devx.ci.pr_review \
"$PR_NUMBER" \
"$REPOSITORY" \
--event APPROVE \
--checklist-confirmed \
--checklist-categories 1,2,3,4,5,6,7,8,9,10,11,12,13 \
--body "Auto-approved: all CI checks passed (quality, pr-review, release-dry-run)."
- name: Squash merge with task ID - name: Squash merge with task ID
env: env:
CI_GITEA_TOKEN: ${{ secrets.CI_GITEA_TOKEN }} CI_GITEA_TOKEN: ${{ secrets.CI_GITEA_TOKEN }}
+12
View File
@@ -2,6 +2,18 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
## [0.26.3] - 2026-06-28
### Bug Fixes
- Pin all dependencies to exact versions for reproducibility
## [0.26.2] - 2026-06-28
### Bug Fixes
- Block admin merge override and auto-approve with review token
## [0.26.1] - 2026-06-28 ## [0.26.1] - 2026-06-28
### Bug Fixes ### Bug Fixes
+6 -6
View File
@@ -16,12 +16,12 @@ quality badges.
[![CI](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/actions/workflows/ci.yml/badge.svg)](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/actions) [![CI](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/actions/workflows/ci.yml/badge.svg)](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/actions)
[![License: GPL-3.0](https://img.shields.io/badge/license-GPL--3.0-blue)](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/src/branch/master/LICENSE) [![License: GPL-3.0](https://img.shields.io/badge/license-GPL--3.0-blue)](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/src/branch/master/LICENSE)
[![Coverage](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/raw/commit/7dc6d2ce5799f3261c6527176478f19a0105e07c/coverage.svg)](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/actions) [![Coverage](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/raw/commit/c04f826e43273dbc92325b851c49fe9b4a4b2a76/coverage.svg)](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/actions)
[![Tests](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/raw/commit/7dc6d2ce5799f3261c6527176478f19a0105e07c/tests.svg)](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/actions) [![Tests](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/raw/commit/c04f826e43273dbc92325b851c49fe9b4a4b2a76/tests.svg)](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/actions)
[![Docs](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/raw/commit/7dc6d2ce5799f3261c6527176478f19a0105e07c/docs.svg)](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/wiki) [![Docs](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/raw/commit/c04f826e43273dbc92325b851c49fe9b4a4b2a76/docs.svg)](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/wiki)
[![Code Quality](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/raw/commit/7dc6d2ce5799f3261c6527176478f19a0105e07c/quality.svg)](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/actions) [![Code Quality](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/raw/commit/c04f826e43273dbc92325b851c49fe9b4a4b2a76/quality.svg)](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/actions)
[![Version](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/raw/commit/7dc6d2ce5799f3261c6527176478f19a0105e07c/version.svg)](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/releases) [![Version](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/raw/commit/c04f826e43273dbc92325b851c49fe9b4a4b2a76/version.svg)](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/releases)
[![Python](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/raw/commit/7dc6d2ce5799f3261c6527176478f19a0105e07c/python.svg)](https://www.python.org/downloads/) [![Python](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/raw/commit/c04f826e43273dbc92325b851c49fe9b4a4b2a76/python.svg)](https://www.python.org/downloads/)
## Why devx? ## Why devx?
+6 -6
View File
@@ -12,12 +12,12 @@ project to be reusable across all oblachno-oss repositories.
[![CI](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/actions/workflows/ci.yml/badge.svg)](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/actions) [![CI](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/actions/workflows/ci.yml/badge.svg)](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/actions)
[![License: GPL-3.0](https://img.shields.io/badge/license-GPL--3.0-blue)](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/src/branch/master/LICENSE) [![License: GPL-3.0](https://img.shields.io/badge/license-GPL--3.0-blue)](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/src/branch/master/LICENSE)
[![Coverage](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/raw/commit/7dc6d2ce5799f3261c6527176478f19a0105e07c/coverage.svg)](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/actions) [![Coverage](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/raw/commit/c04f826e43273dbc92325b851c49fe9b4a4b2a76/coverage.svg)](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/actions)
[![Tests](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/raw/commit/7dc6d2ce5799f3261c6527176478f19a0105e07c/tests.svg)](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/actions) [![Tests](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/raw/commit/c04f826e43273dbc92325b851c49fe9b4a4b2a76/tests.svg)](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/actions)
[![Docs](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/raw/commit/7dc6d2ce5799f3261c6527176478f19a0105e07c/docs.svg)](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/wiki) [![Docs](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/raw/commit/c04f826e43273dbc92325b851c49fe9b4a4b2a76/docs.svg)](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/wiki)
[![Code Quality](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/raw/commit/7dc6d2ce5799f3261c6527176478f19a0105e07c/quality.svg)](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/actions) [![Code Quality](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/raw/commit/c04f826e43273dbc92325b851c49fe9b4a4b2a76/quality.svg)](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/actions)
[![Version](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/raw/commit/7dc6d2ce5799f3261c6527176478f19a0105e07c/version.svg)](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/releases) [![Version](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/raw/commit/c04f826e43273dbc92325b851c49fe9b4a4b2a76/version.svg)](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/releases)
[![Python](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/raw/commit/7dc6d2ce5799f3261c6527176478f19a0105e07c/python.svg)](https://www.python.org/downloads/) [![Python](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/raw/commit/c04f826e43273dbc92325b851c49fe9b4a4b2a76/python.svg)](https://www.python.org/downloads/)
## Overview ## Overview
+28 -26
View File
@@ -13,11 +13,13 @@ classifiers = [
"Programming Language :: Python :: 3", "Programming Language :: Python :: 3",
"License :: OSI Approved :: GNU General Public License v3 (GPLv3)", "License :: OSI Approved :: GNU General Public License v3 (GPLv3)",
] ]
# All dependencies are pinned to exact versions for full reproducibility.
# Update pinned versions in a dedicated PR with verification.
dependencies = [ dependencies = [
"requests>=2.34.2", "requests==2.34.2",
"python-dotenv>=1.2.2", "python-dotenv==1.2.2",
"click>=8.4.1", "click==8.4.2",
"tenacity>=8.2", # retry logic for GiteaClient/VikunjaClient "tenacity==9.1.4", # retry logic for GiteaClient/VikunjaClient
] ]
[project.scripts] [project.scripts]
@@ -29,44 +31,44 @@ version = {attr = "devx.__version__"}
[project.optional-dependencies] [project.optional-dependencies]
# Test runners (pytest + coverage + parallel execution) # Test runners (pytest + coverage + parallel execution)
ci = [ ci = [
"pytest>=9.1.0", "pytest==9.1.1",
"pytest-cov>=7.1.0", "pytest-cov==7.1.0",
"pytest-xdist>=3.8", "pytest-xdist==3.8.0",
] ]
# Lint and type-checking tools (quality job, badge generation) # Lint and type-checking tools (quality job, badge generation)
lint = [ lint = [
"ruff>=0.15.17", "ruff==0.15.20",
"pyright>=1.1.410", "pyright==1.1.411",
"bandit>=1.8.2", "bandit==1.9.4",
"pip-audit>=2.10", "pip-audit==2.10.1",
"pre-commit>=4.6.0", "pre-commit==4.6.0",
] ]
# Release tools (build + publish to PyPI/Gitea registry) # Release tools (build + publish to PyPI/Gitea registry)
release = [ release = [
"build>=1.5.0", "build==1.5.0",
"twine>=6.2.0", "twine==6.2.0",
] ]
# Molecule testing (for projects with Ansible roles) # Molecule testing (for projects with Ansible roles)
molecule = [ molecule = [
"molecule>=26.4.0", "molecule==26.4.0",
"molecule-docker>=2.1.0", "molecule-docker==2.1.0",
"ansible-lint>=26.4.0", "ansible-lint==26.4.0",
"ansible-core>=2.15,<2.17", "ansible-core==2.21.1",
] ]
# Deploy tools (for infra staging/production deployments) # Deploy tools (for infra staging/production deployments)
deploy = [ deploy = [
"ansible-core>=2.15,<2.17", "ansible-core==2.21.1",
"boto3>=1.34", "boto3==1.43.36",
"docker>=7.0", "docker==7.1.0",
"jinja2>=3.1", "jinja2==3.1.6",
"pyyaml>=6.0", "pyyaml==6.0.3",
"cryptography>=41.0", "cryptography==49.0.0",
] ]
# Full dev environment (local development) # Full dev environment (local development)
dev = [ dev = [
"devx[ci,lint,release,molecule]", "devx[ci,lint,release,molecule]",
"build>=1.3.0", "build==1.5.0",
"twine>=6.2.0", "twine==6.2.0",
] ]
[tool.setuptools.packages.find] [tool.setuptools.packages.find]
+1 -1
View File
@@ -1,3 +1,3 @@
"""devx — reusable development and CI/CD tools for oblachno-oss projects.""" """devx — reusable development and CI/CD tools for oblachno-oss projects."""
__version__ = "0.26.1" __version__ = "0.26.3"
+7
View File
@@ -55,6 +55,12 @@ def _default_branch_protection_config() -> dict[str, Any]:
"block_on_outdated_branch": True, "block_on_outdated_branch": True,
"block_on_rejected_reviews": True, "block_on_rejected_reviews": True,
"block_on_official_review_requests": True, "block_on_official_review_requests": True,
# Prevent admins from force-merging PRs that don't meet branch
# protection requirements (e.g. missing approvals). Without this,
# an admin token can bypass the approval gate via force_merge=true,
# allowing merges that failed the auto-merge CI job to reach master
# and trigger the post-merge release pipeline.
"block_admin_merge_override": True,
} }
@@ -123,6 +129,7 @@ def configure_repo(
click.echo(_(" - Dismiss stale approvals: yes")) click.echo(_(" - Dismiss stale approvals: yes"))
click.echo(_(" - Block outdated branches: yes")) click.echo(_(" - Block outdated branches: yes"))
click.echo(_(" - Block rejected reviews: yes")) click.echo(_(" - Block rejected reviews: yes"))
click.echo(_(" - Block admin merge override: yes"))
checks = ", ".join(cast(list[str], bp_config["status_check_contexts"])) checks = ", ".join(cast(list[str], bp_config["status_check_contexts"]))
click.echo(_(" - Required status checks: {checks}", checks=checks)) click.echo(_(" - Required status checks: {checks}", checks=checks))
+1 -1
View File
@@ -348,7 +348,7 @@ def collect_quality(repo_root: Path) -> dict[str, str | int]:
([sys.executable, "-m", "pyright"], "pyright"), ([sys.executable, "-m", "pyright"], "pyright"),
([sys.executable, "-m", "bandit", "-r", "src/"], "bandit"), ([sys.executable, "-m", "bandit", "-r", "src/"], "bandit"),
]: ]:
rc, _, stderr = run_command(cmd, cwd=repo_root) rc, _stdout, stderr = run_command(cmd, cwd=repo_root)
if rc == 0: if rc == 0:
results.append(True) results.append(True)
tool_names.append(f"{name}: pass") tool_names.append(f"{name}: pass")
+8
View File
@@ -2270,5 +2270,13 @@
"pl": "Review body must be at least 50 characters.", "pl": "Review body must be at least 50 characters.",
"ru": "Review body must be at least 50 characters.", "ru": "Review body must be at least 50 characters.",
"zh": "Review body must be at least 50 characters." "zh": "Review body must be at least 50 characters."
},
" - Block admin merge override: yes": {
"bg": " - Блокиране на admin merge override: да",
"de": " - Admin-Merge-Override blockieren: ja",
"en": " - Block admin merge override: yes",
"pl": " - Blokuj admin merge override: tak",
"ru": " - Блокировать admin merge override: да",
"zh": " - 阻止管理员合并覆盖:是"
} }
} }
+1
View File
@@ -35,6 +35,7 @@ class TestDefaultConfigs:
assert config["required_approvals"] == 1 assert config["required_approvals"] == 1
assert isinstance(config["status_check_contexts"], list) assert isinstance(config["status_check_contexts"], list)
assert "CI / quality (pull_request)" in config["status_check_contexts"] assert "CI / quality (pull_request)" in config["status_check_contexts"]
assert config["block_admin_merge_override"] is True
def test_default_repo_settings_config(self) -> None: def test_default_repo_settings_config(self) -> None:
config = _default_repo_settings_config() config = _default_repo_settings_config()