Public Access
DEVX-9: fix: configurable task prefix and CWD-relative DOCS_DIR
This commit is contained in:
@@ -34,7 +34,13 @@ from devx.i18n import _
|
||||
|
||||
load_dotenv()
|
||||
|
||||
DOCS_DIR = Path(__file__).resolve().parent.parent.parent.parent / "docs"
|
||||
# DOCS_DIR is the repo's docs/ directory. When devx is installed as a
|
||||
# package (e.g., in .venv/lib/python3.12/site-packages/devx/), the
|
||||
# __file__-relative path would point inside the venv, not the repo.
|
||||
# Use DEVX_DOCS_DIR env var if set, otherwise fall back to ./docs
|
||||
# (relative to the current working directory, which is the repo root
|
||||
# in CI and local development).
|
||||
DOCS_DIR = Path(os.environ.get("DEVX_DOCS_DIR", "docs"))
|
||||
MAPPING_FILE = DOCS_DIR / "mapping.json"
|
||||
|
||||
|
||||
|
||||
@@ -2,9 +2,14 @@
|
||||
"""Validate commit messages for devx.
|
||||
|
||||
Rules:
|
||||
- On feature branches: conventional commits ONLY, must NOT include DEVX-N prefix.
|
||||
- On feature branches: conventional commits ONLY, must NOT include <PREFIX>-N prefix.
|
||||
- On master branch: must follow '<task-id>: <conventional commit>' pattern,
|
||||
e.g. 'DEVX-24: fix: resolve timeout'.
|
||||
|
||||
The task ID prefix is configurable via the ``DEVX_TASK_PREFIX`` environment
|
||||
variable (default: ``DEVX``). Projects consuming devx (e.g., GRM) set
|
||||
their own prefix (e.g., ``GRM``) so the validator enforces the correct
|
||||
task ID format for each project.
|
||||
"""
|
||||
|
||||
import re
|
||||
@@ -12,10 +17,10 @@ import subprocess # nosec B404
|
||||
|
||||
import click
|
||||
|
||||
from devx.config import CONVENTIONAL_RE
|
||||
from devx.config import CONVENTIONAL_RE, TASK_PREFIX
|
||||
from devx.i18n import _
|
||||
|
||||
MASTER_TASK_ID_RE = re.compile(r"^DEVX-\d+:")
|
||||
MASTER_TASK_ID_RE = re.compile(rf"^{TASK_PREFIX}-\d+:")
|
||||
|
||||
|
||||
def first_line(text: str) -> str:
|
||||
@@ -51,8 +56,9 @@ def main(commit_msg_file: str, branch: str | None) -> None:
|
||||
raise click.ClickException(
|
||||
_(
|
||||
"Oops! Master branch commits must start with a task ID.\n"
|
||||
" Expected: DEVX-N: <conventional commit message>\n"
|
||||
" Expected: {prefix}-N: <conventional commit message>\n"
|
||||
" Got: {subject}",
|
||||
prefix=TASK_PREFIX,
|
||||
subject=subject,
|
||||
)
|
||||
)
|
||||
@@ -61,8 +67,9 @@ def main(commit_msg_file: str, branch: str | None) -> None:
|
||||
raise click.ClickException(
|
||||
_(
|
||||
"Oops! Master branch commit must follow conventional format after task ID.\n"
|
||||
" Expected: DEVX-N: <type>: <description>\n"
|
||||
" Expected: {prefix}-N: <type>: <description>\n"
|
||||
" Got: {subject}",
|
||||
prefix=TASK_PREFIX,
|
||||
subject=subject,
|
||||
)
|
||||
)
|
||||
@@ -71,8 +78,9 @@ def main(commit_msg_file: str, branch: str | None) -> None:
|
||||
if MASTER_TASK_ID_RE.match(subject):
|
||||
raise click.ClickException(
|
||||
_(
|
||||
"Oops! Do not include task ID (DEVX-N) in feature branch commits.\n"
|
||||
" The task ID will be added automatically on merge via CI."
|
||||
"Oops! Do not include task ID ({prefix}-N) in feature branch commits.\n"
|
||||
" The task ID will be added automatically on merge via CI.",
|
||||
prefix=TASK_PREFIX,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
+21
-21
@@ -559,13 +559,6 @@
|
||||
"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! Gitea PyPI registry publish failed:\n{stderr}": {
|
||||
"en": "Oops! Gitea PyPI registry publish failed:\n{stderr}",
|
||||
"bg": "Опа! Публикуването в Gitea PyPI registry неуспешно:\n{stderr}",
|
||||
@@ -573,20 +566,6 @@
|
||||
"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 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! No task ID found in .taskid file or branch name '{branch}'.": {
|
||||
"en": "Oops! No task ID found in .taskid file or branch name '{branch}'.",
|
||||
"bg": "Oops! No task ID found in .taskid file or branch name '{branch}'.",
|
||||
@@ -1041,5 +1020,26 @@
|
||||
"de": "git-cliff returned invalid version format: {version}. Expected semver (e.g., 0.4.1).",
|
||||
"ru": "git-cliff returned invalid version format: {version}. Expected semver (e.g., 0.4.1).",
|
||||
"zh": "git-cliff returned invalid version format: {version}. Expected semver (e.g., 0.4.1)."
|
||||
},
|
||||
"Oops! Do not include task ID ({prefix}-N) in feature branch commits.\n The task ID will be added automatically on merge via CI.": {
|
||||
"en": "Oops! Do not include task ID ({prefix}-N) in feature branch commits.\n The task ID will be added automatically on merge via CI.",
|
||||
"bg": "Oops! Do not include task ID ({prefix}-N) in feature branch commits.\n The task ID will be added automatically on merge via CI.",
|
||||
"de": "Oops! Do not include task ID ({prefix}-N) in feature branch commits.\n The task ID will be added automatically on merge via CI.",
|
||||
"ru": "Oops! Do not include task ID ({prefix}-N) in feature branch commits.\n The task ID will be added automatically on merge via CI.",
|
||||
"zh": "Oops! Do not include task ID ({prefix}-N) in feature branch commits.\n The task ID will be added automatically on merge via CI."
|
||||
},
|
||||
"Oops! Master branch commit must follow conventional format after task ID.\n Expected: {prefix}-N: <type>: <description>\n Got: {subject}": {
|
||||
"en": "Oops! Master branch commit must follow conventional format after task ID.\n Expected: {prefix}-N: <type>: <description>\n Got: {subject}",
|
||||
"bg": "Oops! Master branch commit must follow conventional format after task ID.\n Expected: {prefix}-N: <type>: <description>\n Got: {subject}",
|
||||
"de": "Oops! Master branch commit must follow conventional format after task ID.\n Expected: {prefix}-N: <type>: <description>\n Got: {subject}",
|
||||
"ru": "Oops! Master branch commit must follow conventional format after task ID.\n Expected: {prefix}-N: <type>: <description>\n Got: {subject}",
|
||||
"zh": "Oops! Master branch commit must follow conventional format after task ID.\n Expected: {prefix}-N: <type>: <description>\n Got: {subject}"
|
||||
},
|
||||
"Oops! Master branch commits must start with a task ID.\n Expected: {prefix}-N: <conventional commit message>\n Got: {subject}": {
|
||||
"en": "Oops! Master branch commits must start with a task ID.\n Expected: {prefix}-N: <conventional commit message>\n Got: {subject}",
|
||||
"bg": "Oops! Master branch commits must start with a task ID.\n Expected: {prefix}-N: <conventional commit message>\n Got: {subject}",
|
||||
"de": "Oops! Master branch commits must start with a task ID.\n Expected: {prefix}-N: <conventional commit message>\n Got: {subject}",
|
||||
"ru": "Oops! Master branch commits must start with a task ID.\n Expected: {prefix}-N: <conventional commit message>\n Got: {subject}",
|
||||
"zh": "Oops! Master branch commits must start with a task ID.\n Expected: {prefix}-N: <conventional commit message>\n Got: {subject}"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -152,6 +152,89 @@ class TestMain:
|
||||
assert "task ID" in result.output
|
||||
|
||||
|
||||
class TestCustomPrefix:
|
||||
"""Tests for custom task ID prefix (e.g., GRM-N instead of DEVX-N).
|
||||
|
||||
The prefix is configured via the DEVX_TASK_PREFIX environment variable.
|
||||
This is critical for consumer projects like GRM that use their own
|
||||
Vikunja project with a different identifier prefix.
|
||||
"""
|
||||
|
||||
def _write_msg(self, content: str) -> str:
|
||||
fd, path = tempfile.mkstemp()
|
||||
with os.fdopen(fd, "w") as f:
|
||||
f.write(content)
|
||||
return path
|
||||
|
||||
@patch.dict("os.environ", {"DEVX_TASK_PREFIX": "GRM"})
|
||||
def test_master_accepts_grm_prefix(self) -> None:
|
||||
"""Master branch accepts GRM-N: prefix when DEVX_TASK_PREFIX=GRM."""
|
||||
import importlib
|
||||
|
||||
import devx.ci.validate_commit_msg as vcm
|
||||
import devx.config
|
||||
|
||||
importlib.reload(devx.config)
|
||||
importlib.reload(vcm)
|
||||
try:
|
||||
msg_path = self._write_msg("GRM-66: fix: add scripts/** to infrastructure")
|
||||
with patch("devx.ci.validate_commit_msg.get_branch", return_value="master"):
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(vcm.main, [msg_path])
|
||||
assert result.exit_code == 0
|
||||
os.unlink(msg_path)
|
||||
finally:
|
||||
os.environ.pop("DEVX_TASK_PREFIX", None)
|
||||
importlib.reload(devx.config)
|
||||
importlib.reload(vcm)
|
||||
|
||||
@patch.dict("os.environ", {"DEVX_TASK_PREFIX": "GRM"})
|
||||
def test_master_rejects_devx_prefix_when_grm_configured(self) -> None:
|
||||
"""Master branch rejects DEVX-N: prefix when DEVX_TASK_PREFIX=GRM."""
|
||||
import importlib
|
||||
|
||||
import devx.ci.validate_commit_msg as vcm
|
||||
import devx.config
|
||||
|
||||
importlib.reload(devx.config)
|
||||
importlib.reload(vcm)
|
||||
try:
|
||||
msg_path = self._write_msg("DEVX-8: fix: wrong prefix")
|
||||
with patch("devx.ci.validate_commit_msg.get_branch", return_value="master"):
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(vcm.main, [msg_path])
|
||||
assert result.exit_code == 1
|
||||
assert "GRM-N" in result.output
|
||||
os.unlink(msg_path)
|
||||
finally:
|
||||
os.environ.pop("DEVX_TASK_PREFIX", None)
|
||||
importlib.reload(devx.config)
|
||||
importlib.reload(vcm)
|
||||
|
||||
@patch.dict("os.environ", {"DEVX_TASK_PREFIX": "GRM"})
|
||||
def test_feature_branch_rejects_grm_prefix(self) -> None:
|
||||
"""Feature branch rejects GRM-N: prefix when DEVX_TASK_PREFIX=GRM."""
|
||||
import importlib
|
||||
|
||||
import devx.ci.validate_commit_msg as vcm
|
||||
import devx.config
|
||||
|
||||
importlib.reload(devx.config)
|
||||
importlib.reload(vcm)
|
||||
try:
|
||||
msg_path = self._write_msg("GRM-66: fix: should not have prefix on branch")
|
||||
with patch("devx.ci.validate_commit_msg.get_branch", return_value="GRM-66-fix"):
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(vcm.main, [msg_path])
|
||||
assert result.exit_code == 1
|
||||
assert "task ID" in result.output
|
||||
os.unlink(msg_path)
|
||||
finally:
|
||||
os.environ.pop("DEVX_TASK_PREFIX", None)
|
||||
importlib.reload(devx.config)
|
||||
importlib.reload(vcm)
|
||||
|
||||
|
||||
def test_main_module_block() -> None:
|
||||
import tempfile
|
||||
|
||||
|
||||
Reference in New Issue
Block a user