DEVX-1: fix: use python3 and venv python in workflows and Makefile

This commit is contained in:
emil
2026-06-22 15:52:45 +00:00
parent 60fd11419c
commit 31bfd23fea
24 changed files with 591 additions and 58 deletions
+1 -1
View File
@@ -41,7 +41,7 @@ from devx.i18n import _
TASKID_FILE = ".taskid"
PR_TITLE_RE = re.compile(r"^DEVX-\d+:\s+.+")
load_dotenv(override=True)
load_dotenv()
def run_cmd(args: list[str], check: bool = True) -> subprocess.CompletedProcess[str]:
+1 -1
View File
@@ -25,7 +25,7 @@ from devx.config import GITEA_API_URL
from devx.gitea_cli import TeaCLI, TeaCLIError
from devx.i18n import _
load_dotenv(override=True)
load_dotenv()
def _create_issue_via_tea(repo: str, title: str, body: str) -> int:
+1 -1
View File
@@ -17,7 +17,7 @@ from devx.config import DEFAULT_PER_PAGE, TASK_ID_RE, VIKUNJA_API_URL, VIKUNJA_P
from devx.exceptions import APIError
from devx.i18n import _
load_dotenv(override=True)
load_dotenv()
def _get_git_commit_message() -> str:
+1 -1
View File
@@ -35,7 +35,7 @@ from devx.config import GITEA_API_URL
from devx.exceptions import APIError
from devx.i18n import _
load_dotenv(override=True)
load_dotenv()
# Files that are exempt from certain checks
WORKFLOW_ONLY_SUFFIXES = (".yml", ".yaml", ".md", ".json", ".toml", ".cfg", ".ini", ".txt")
+1 -1
View File
@@ -31,7 +31,7 @@ from devx.config import GITEA_API_URL
from devx.gitea_cli import TeaCLI, TeaCLIError
from devx.i18n import _
load_dotenv(override=True)
load_dotenv()
CLIFF_CONFIG = "cliff.toml"
+1 -1
View File
@@ -39,7 +39,7 @@ from dotenv import load_dotenv # pyright: ignore[reportMissingImports,reportUnk
from devx.ci.classify_changes import has_user_facing_changes # cross-CI import, needs PYTHONPATH=.
from devx.i18n import _
load_dotenv(override=True)
load_dotenv()
INIT_FILE = os.getenv("DEVX_VERSION_FILE", "src/devx/__init__.py")
CHANGELOG_FILE = "CHANGELOG.md"
+1 -1
View File
@@ -32,7 +32,7 @@ from devx.config import GITEA_API_URL
from devx.exceptions import APIError
from devx.i18n import _
load_dotenv(override=True)
load_dotenv()
DOCS_DIR = Path(__file__).resolve().parent.parent.parent.parent / "docs"
MAPPING_FILE = DOCS_DIR / "mapping.json"
+3 -1
View File
@@ -122,7 +122,9 @@ def install_git_cliff() -> bool:
if _is_installed("git-cliff"):
click.echo("git-cliff: already installed")
return True
arch = _arch()
# git-cliff uses x86_64/arm64 in release asset names (not amd64)
machine = platform.machine().lower()
arch = "x86_64" if machine in {"x86_64", "amd64"} else "arm64"
url = (
f"https://github.com/orhun/git-cliff/releases/download/"
f"v{GIT_CLIFF_VERSION}/git-cliff-{GIT_CLIFF_VERSION}-{arch}-unknown-linux-gnu.tar.gz"
+61 -6
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
"""Project setup: install Python deps and pre-commit hooks.
"""Project setup: install Python deps, pre-commit hooks, and tea CLI login.
Usage::
@@ -8,10 +8,15 @@ Usage::
from __future__ import annotations
import os
import shutil
import subprocess # nosec B404
from pathlib import Path
import click
from dotenv import load_dotenv # pyright: ignore[reportMissingImports,reportUnknownVariableType]
load_dotenv()
def _run(cmd: list[str]) -> None:
@@ -33,6 +38,55 @@ def _install_pre_commit_hooks(bin_dir: str) -> None:
_run([pre_commit, "install", "--hook-type", hook_type])
def _configure_tea_login() -> None:
"""Configure tea CLI login from .env if REPO_TOKEN is set.
Idempotent: if a login with the same name already exists, it is not re-added.
Skips if tea is not installed or REPO_TOKEN is not set.
"""
tea_bin = shutil.which("tea")
if tea_bin is None:
click.echo("tea: not installed — run 'make install-tools' to install it.")
return
token = os.environ.get("REPO_TOKEN", "")
if not token:
click.echo("tea: REPO_TOKEN not set — skipping login configuration.")
return
api_url = os.environ.get("DEVX_GITEA_API_URL", "https://git.oblachno.oblachno.fyi/api/v1")
gitea_url = api_url.replace("/api/v1", "")
login_name = "devx"
result = subprocess.run( # nosec B603
[tea_bin, "login", "list", "--output", "simple"],
capture_output=True,
text=True,
check=False,
)
if result.returncode == 0 and login_name in result.stdout:
click.echo(f"tea: login '{login_name}' already configured.")
return
click.echo(f"tea: configuring login '{login_name}' for {gitea_url}...")
add_result = subprocess.run( # nosec B603
[tea_bin, "login", "add", "--name", login_name, "--url", gitea_url, "--token", token],
capture_output=True,
text=True,
check=False,
)
if add_result.returncode != 0:
click.echo(f"tea: login configuration failed: {add_result.stderr.strip()}", err=True)
else:
subprocess.run( # nosec B603
[tea_bin, "login", "default", login_name],
capture_output=True,
text=True,
check=False,
)
click.echo(f"tea: login '{login_name}' configured and set as default.")
def _verify(bin_dir: str) -> None:
"""Print versions of installed tools for verification."""
devx = str(Path(bin_dir) / "devx")
@@ -51,7 +105,7 @@ def _verify(bin_dir: str) -> None:
@click.option(
"--extras",
default="dev",
help="Dependency group to install: ci, lint, build, twine, or dev (default: dev).",
help="Dependency group to install: ci, lint, or dev (default: dev).",
)
@click.option(
"--no-pre-commit",
@@ -63,7 +117,7 @@ def _verify(bin_dir: str) -> None:
"--no-tea-login",
is_flag=True,
default=False,
help="Skip tea CLI login configuration (no-op, kept for backwards compatibility).",
help="Skip tea CLI login configuration.",
)
def main(
bin_dir: str,
@@ -71,7 +125,7 @@ def main(
no_pre_commit: bool,
no_tea_login: bool,
) -> None:
"""Install Python deps and pre-commit hooks."""
"""Install Python deps, pre-commit hooks, and configure tea CLI."""
if not Path(bin_dir).exists():
raise click.ClickException(f"Bin directory not found: {bin_dir}. Run 'python3 -m venv .venv' first.")
@@ -82,8 +136,9 @@ def main(
click.echo("Installing pre-commit hooks...")
_install_pre_commit_hooks(bin_dir)
# --no-tea-login is a no-op (kept for backwards compatibility)
_ = no_tea_login
if not no_tea_login:
click.echo("Configuring tea CLI login...")
_configure_tea_login()
click.echo("")
click.echo("Setup complete.")
+140
View File
@@ -110,6 +110,13 @@
" FAIL: {title} — content mismatch or empty!": {
"en": " FAIL: {title} — content mismatch or empty!"
},
" MISSING: devx {cmd}": {
"en": " MISSING: devx {cmd}",
"bg": " ЛИПСВА: devx {cmd}",
"de": " FEHLT: devx {cmd}",
"ru": " ОТСУТСТВУЕТ: devx {cmd}",
"zh": " 缺失: devx {cmd}"
},
" MISSING: grm {cmd}": {
"en": " MISSING: grm {cmd}"
},
@@ -119,6 +126,13 @@
" MISSING: {script}": {
"en": " MISSING: {script}"
},
" OK: devx {cmd}": {
"en": " OK: devx {cmd}",
"bg": " ОК: devx {cmd}",
"de": " OK: devx {cmd}",
"ru": " ОК: devx {cmd}",
"zh": " 正常: devx {cmd}"
},
" OK: grm {cmd}": {
"en": " OK: grm {cmd}"
},
@@ -204,6 +218,13 @@
"ru": "ОШИБКА: VIKUNJA_TOKEN не задан.",
"zh": "错误:未设置 VIKUNJA_TOKEN。"
},
"ERROR: Repository name not specified. Use --repo or set DEVX_REPO_NAME.": {
"en": "ERROR: Repository name not specified. Use --repo or set DEVX_REPO_NAME.",
"bg": "ГРЕШКА: Името на хранилището не е указано. Използвайте --repo или задайте DEVX_REPO_NAME.",
"de": "FEHLER: Repository-Name nicht angegeben. Verwenden Sie --repo oder setzen Sie DEVX_REPO_NAME.",
"ru": "ОШИБКА: Имя репозитория не указано. Используйте --repo или задайте DEVX_REPO_NAME.",
"zh": "错误:未指定仓库名称。请使用 --repo 或设置 DEVX_REPO_NAME。"
},
"ERROR: mapping.json not found at {path}": {
"en": "ERROR: mapping.json not found at {path}"
},
@@ -239,6 +260,13 @@
"Head branch is behind master. Pulling and rebasing...": {
"en": "Head branch is behind master. Pulling and rebasing..."
},
"Infrastructure commit (no DEVX-N task ID), skipping Vikunja update: {msg}": {
"en": "Infrastructure commit (no DEVX-N task ID), skipping Vikunja update: {msg}",
"bg": "Инфраструктурен commit (без идентификатор на задача DEVX-N), пропускаме обновяването на Vikunja: {msg}",
"de": "Infrastruktur-Commit (keine DEVX-N Task-ID), Vikunja-Update wird übersprungen: {msg}",
"ru": "Инфраструктурный коммит (без ID задачи DEVX-N), пропуск обновления Vikunja: {msg}",
"zh": "基础设施提交(无 DEVX-N 任务 ID),跳过 Vikunja 更新: {msg}"
},
"Infrastructure commit (no GRM-N task ID), skipping Vikunja update: {msg}": {
"en": "Infrastructure commit (no GRM-N task ID), skipping Vikunja update: {msg}"
},
@@ -258,6 +286,13 @@
"ru": "Слияние не удалось: HTTP {status}: {message}\nПроверьте, что PR готов и у вас есть права на слияние.",
"zh": "合并失败: HTTP {status}: {message}\n请检查 PR 是否准备就绪且您具有合并权限。"
},
"Module {mod} has no main() function": {
"en": "Module {mod} has no main() function",
"bg": "Модул {mod} няма функция main()",
"de": "Modul {mod} hat keine main()-Funktion",
"ru": "Модуль {mod} не имеет функции main()",
"zh": "模块 {mod} 没有 main() 函数"
},
"Molecule directory not found: {path}": {
"en": "Molecule directory not found: {path}",
"bg": "Директорията на molecule не е намерена: {path}",
@@ -314,6 +349,13 @@
"ru": "Ой! Сообщение коммита должно соответствовать формату conventional commit.\n Ожидается: <type>: <description>\n Получено: {subject}\n Допустимые типы: feat, fix, chore, docs, style, refactor,\n perf, test, ci, build, revert, BREAKING CHANGE",
"zh": "哎呀!提交消息必须遵循 conventional commit 格式。\n 预期格式: <type>: <description>\n 实际: {subject}\n 允许的类型: feat, fix, chore, docs, style, refactor,\n perf, test, ci, build, revert, BREAKING CHANGE"
},
"Oops! Do not include task ID (DEVX-N) in feature branch commits.\n The task ID will be added automatically on merge via CI.": {
"en": "Oops! Do not include task ID (DEVX-N) in feature branch commits.\n The task ID will be added automatically on merge via CI.",
"bg": "Опа! Не включвайте идентификатор на задача (DEVX-N) в commit-и от feature клонове.\n Идентификаторът ще бъде добавен автоматично при сливане чрез CI.",
"de": "Ups! Keine Task-ID (DEVX-N) in Feature-Branch-Commits einfügen.\n Die Task-ID wird beim Merge automatisch über CI hinzugefügt.",
"ru": "Ой! Не включайте ID задачи (DEVX-N) в коммиты feature-веток.\n ID задачи будет добавлен автоматически при слиянии через CI.",
"zh": "哎呀!不要在 feature 分支的提交中包含任务 ID (DEVX-N)。\n 任务 ID 将在通过 CI 合并时自动添加。"
},
"Oops! Do not include task ID (GRM-N) in feature branch commits.\n The task ID will be added automatically on merge via CI.": {
"en": "Oops! Do not include task ID (GRM-N) in feature branch commits.\n The task ID will be added automatically on merge via CI.",
"bg": "Опа! Не включвайте идентификатор на задача (GRM-N) в commit-и от feature клонове.\n Идентификаторът ще бъде добавен автоматично при сливане чрез CI.",
@@ -321,6 +363,20 @@
"ru": "Ой! Не включайте ID задачи (GRM-N) в коммиты feature-веток.\n ID задачи будет добавлен автоматически при слиянии через CI.",
"zh": "哎呀!不要在 feature 分支的提交中包含任务 ID (GRM-N)。\n 任务 ID 将在通过 CI 合并时自动添加。"
},
"Oops! Gitea PyPI registry publish failed:\n{stderr}": {
"en": "Oops! Gitea PyPI registry publish failed:\n{stderr}",
"bg": "Опа! Публикуването в Gitea PyPI registry неуспешно:\n{stderr}",
"de": "Ups! Veröffentlichung in der Gitea PyPI-Registry fehlgeschlagen:\n{stderr}",
"ru": "Ой! Публикация в Gitea PyPI registry не удалась:\n{stderr}",
"zh": "哎呀!Gitea PyPI registry 发布失败:\n{stderr}"
},
"Oops! Master branch commit must follow conventional format after task ID.\n Expected: DEVX-N: <type>: <description>\n Got: {subject}": {
"en": "Oops! Master branch commit must follow conventional format after task ID.\n Expected: DEVX-N: <type>: <description>\n Got: {subject}",
"bg": "Опа! Commit-ът в клона master трябва да следва конвенционален формат след идентификатора.\n Очаква се: DEVX-N: <type>: <description>\n Получено: {subject}",
"de": "Ups! Master-Branch-Commit muss nach der Task-ID dem konventionellen Format folgen.\n Erwartet: DEVX-N: <type>: <description>\n Erhalten: {subject}",
"ru": "Ой! Коммит в ветку master после ID задачи должен соответствовать conventional формату.\n Ожидается: DEVX-N: <type>: <description>\n Получено: {subject}",
"zh": "哎呀!master 分支提交在任务 ID 后必须遵循 conventional commit 格式。\n 预期格式: DEVX-N: <type>: <description>\n 实际: {subject}"
},
"Oops! Master branch commit must follow conventional format after task ID.\n Expected: GRM-N: <type>: <description>\n Got: {subject}": {
"en": "Oops! Master branch commit must follow conventional format after task ID.\n Expected: GRM-N: <type>: <description>\n Got: {subject}",
"bg": "Опа! Commit-ът в клона master трябва да следва конвенционален формат след идентификатора.\n Очаква се: GRM-N: <type>: <description>\n Получено: {subject}",
@@ -328,6 +384,13 @@
"ru": "Ой! Коммит в ветку master после ID задачи должен соответствовать conventional формату.\n Ожидается: GRM-N: <type>: <description>\n Получено: {subject}",
"zh": "哎呀!master 分支提交在任务 ID 后必须遵循 conventional commit 格式。\n 预期格式: GRM-N: <type>: <description>\n 实际: {subject}"
},
"Oops! Master branch commits must start with a task ID.\n Expected: DEVX-N: <conventional commit message>\n Got: {subject}": {
"en": "Oops! Master branch commits must start with a task ID.\n Expected: DEVX-N: <conventional commit message>\n Got: {subject}",
"bg": "Опа! Commit-ите в клона master трябва да започват с идентификатор на задача.\n Очаква се: DEVX-N: <conventional commit message>\n Получено: {subject}",
"de": "Ups! Master-Branch-Commits müssen mit einer Task-ID beginnen.\n Erwartet: DEVX-N: <conventional commit message>\n Erhalten: {subject}",
"ru": "Ой! Коммиты в ветку master должны начинаться с ID задачи.\n Ожидается: DEVX-N: <conventional commit message>\n Получено: {subject}",
"zh": "哎呀!master 分支的提交必须以任务 ID 开头。\n 预期格式: DEVX-N: <conventional commit message>\n 实际: {subject}"
},
"Oops! Master branch commits must start with a task ID.\n Expected: GRM-N: <conventional commit message>\n Got: {subject}": {
"en": "Oops! Master branch commits must start with a task ID.\n Expected: GRM-N: <conventional commit message>\n Got: {subject}",
"bg": "Опа! Commit-ите в клона master трябва да започват с идентификатор на задача.\n Очаква се: GRM-N: <conventional commit message>\n Получено: {subject}",
@@ -338,6 +401,13 @@
"Oops! No task ID found in .taskid file or branch name '{branch}'.": {
"en": "Oops! No task ID found in .taskid file or branch name '{branch}'."
},
"Oops! PR title must follow format 'DEVX-N: <task title>'.\n Expected: {task_id}: <task title>\n Got: {pr_title}": {
"en": "Oops! PR title must follow format 'DEVX-N: <task title>'.\n Expected: {task_id}: <task title>\n Got: {pr_title}",
"bg": "Опа! Заглавието на PR трябва да следва формата 'DEVX-N: <заглавие на задача>'.\n Очаква се: {task_id}: <заглавие на задача>\n Получено: {pr_title}",
"de": "Ups! PR-Titel muss dem Format 'DEVX-N: <Task-Titel>' folgen.\n Erwartet: {task_id}: <Task-Titel>\n Erhalten: {pr_title}",
"ru": "Ой! Заголовок PR должен соответствовать формату 'DEVX-N: <название задачи>'.\n Ожидается: {task_id}: <название задачи>\n Получено: {pr_title}",
"zh": "哎呀!PR 标题必须遵循格式 'DEVX-N: <任务标题>'。\n 预期格式: {task_id}: <任务标题>\n 实际: {pr_title}"
},
"Oops! PR title must follow format 'GRM-N: <task title>'.\n Expected: {task_id}: <task title>\n Got: {pr_title}": {
"en": "Oops! PR title must follow format 'GRM-N: <task title>'.\n Expected: {task_id}: <task title>\n Got: {pr_title}"
},
@@ -364,6 +434,13 @@
"PR title does not match Vikunja task title.\n Expected: {expected}\n Got: {pr_title}": {
"en": "PR title does not match Vikunja task title.\n Expected: {expected}\n Got: {pr_title}"
},
"PYPI_TOKEN not set and no registry URL configured — skipping PyPI publish. No worries, we'll just create the Gitea release.": {
"en": "PYPI_TOKEN not set and no registry URL configured — skipping PyPI publish. No worries, we'll just create the Gitea release.",
"bg": "PYPI_TOKEN не е зададен и няма конфигуриран URL на registry — пропускаме публикуването в PyPI. Без притеснения, просто ще създадем Gitea release.",
"de": "PYPI_TOKEN nicht gesetzt und keine Registry-URL konfiguriert — PyPI-Veröffentlichung wird übersprungen. Keine Sorge, wir erstellen einfach das Gitea-Release.",
"ru": "PYPI_TOKEN не задан и URL registry не настроен — пропускаем публикацию в PyPI. Не беспокойтесь, мы просто создадим Gitea release.",
"zh": "未设置 PYPI_TOKEN 且未配置 registry URL — 跳过 PyPI 发布。别担心,我们直接创建 Gitea release。"
},
"PYPI_TOKEN not set — skipping PyPI publish. No worries, we'll just create the Gitea release.": {
"en": "PYPI_TOKEN not set — skipping PyPI publish. No worries, we'll just create the Gitea release.",
"bg": "PYPI_TOKEN не е зададен — пропускаме публикуването в PyPI. Без притеснения, просто ще създадем Gitea release.",
@@ -371,6 +448,13 @@
"ru": "PYPI_TOKEN не задан — пропускаем публикацию в PyPI. Не беспокойтесь, мы просто создадим Gitea release.",
"zh": "未设置 PYPI_TOKEN — 跳过 PyPI 发布。别担心,我们直接创建 Gitea release。"
},
"Published to Gitea PyPI registry.": {
"en": "Published to Gitea PyPI registry.",
"bg": "Публикувано в Gitea PyPI registry.",
"de": "In der Gitea PyPI-Registry veröffentlicht.",
"ru": "Опубликовано в Gitea PyPI registry.",
"zh": "已发布到 Gitea PyPI registry。"
},
"Published to PyPI.": {
"en": "Published to PyPI.",
"bg": "Публикувано в PyPI.",
@@ -455,6 +539,13 @@
"WARNING: File {file} not found — skipping.": {
"en": "WARNING: File {file} not found — skipping."
},
"Warning: No task ID (DEVX-N) found in commit message: {msg}. Skipping Vikunja update.": {
"en": "Warning: No task ID (DEVX-N) found in commit message: {msg}. Skipping Vikunja update.",
"bg": "Предупреждение: Не е намерен идентификатор на задача (DEVX-N) в съобщението за commit: {msg}. Пропускаме обновяването на Vikunja.",
"de": "Warnung: Keine Task-ID (DEVX-N) in Commit-Nachricht gefunden: {msg}. Vikunja-Update wird übersprungen.",
"ru": "Предупреждение: ID задачи (DEVX-N) не найден в сообщении коммита: {msg}. Пропуск обновления Vikunja.",
"zh": "警告:提交消息中未找到任务 ID (DEVX-N): {msg}。跳过 Vikunja 更新。"
},
"Warning: No task ID (GRM-N) found in commit message: {msg}. Skipping Vikunja update.": {
"en": "Warning: No task ID (GRM-N) found in commit message: {msg}. Skipping Vikunja update."
},
@@ -494,10 +585,59 @@
"[dry-run] Would update {init}": {
"en": "[dry-run] Would update {init}"
},
"active": {
"en": "active",
"bg": "активен",
"de": "aktiv",
"ru": "активен",
"zh": "活跃"
},
"completed": {
"en": "completed",
"bg": "завършен",
"de": "abgeschlossen",
"ru": "завершён",
"zh": "已完成"
},
"failed": {
"en": "failed",
"bg": "неуспешен",
"de": "fehlgeschlagen",
"ru": "неудачный",
"zh": "失败"
},
"git command failed ({cmd}): {stderr}": {
"en": "git command failed ({cmd}): {stderr}"
},
"git-cliff returned empty version.": {
"en": "git-cliff returned empty version."
},
"inactive": {
"en": "inactive",
"bg": "неактивен",
"de": "inaktiv",
"ru": "неактивен",
"zh": "未激活"
},
"in_progress": {
"en": "in progress",
"bg": "в процес",
"de": "in Bearbeitung",
"ru": "в процессе",
"zh": "进行中"
},
"pending": {
"en": "pending",
"bg": "в очакване",
"de": "ausstehend",
"ru": "ожидает",
"zh": "待处理"
},
"unknown": {
"en": "unknown",
"bg": "неизвестен",
"de": "unbekannt",
"ru": "неизвестно",
"zh": "未知"
}
}