From d643007c0b0498e7d11c49e99671a66004550ac4 Mon Sep 17 00:00:00 2001 From: Andrey Epifancev Date: Sat, 24 Oct 2026 11:52:08 +0400 Subject: [PATCH] =?UTF-8?q?feat(sdlc-rename):=20=D0=BE=D1=82=D0=BA=D0=B0?= =?UTF-8?q?=D0=B7=20CLI=20=D0=BF=D1=80=D0=B8=20=D0=BA=D0=B0=D1=82=D0=B0?= =?UTF-8?q?=D0=BB=D0=BE=D0=B3=D0=B5=20=D0=B4=D0=B0=D0=BD=D0=BD=D1=8B=D1=85?= =?UTF-8?q?=20=D0=BF=D0=BE=D0=B4=20=D0=BF=D1=80=D0=B5=D0=B6=D0=BD=D0=B8?= =?UTF-8?q?=D0=BC=20=D0=B8=D0=BC=D0=B5=D0=BD=D0=B5=D0=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Sonnet 5.5 --- src/sdlc_hub/core/cli.py | 6 ++++++ src/sdlc_hub/core/paths.py | 21 +++++++++++++++++++++ tests/conftest.py | 6 ++++++ tests/test_cli.py | 33 +++++++++++++++++++++++++++++++++ 4 files changed, 66 insertions(+) diff --git a/src/sdlc_hub/core/cli.py b/src/sdlc_hub/core/cli.py index 761b364..9693ee3 100644 --- a/src/sdlc_hub/core/cli.py +++ b/src/sdlc_hub/core/cli.py @@ -31,6 +31,12 @@ def main(argv=None) -> int: p.add_argument("path") args = ap.parse_args(argv) + if (problem := paths.home_problem()): + print(problem, file=sys.stderr) + return 2 + if (warning := paths.home_warning()): + print(warning, file=sys.stderr) + if args.cmd == "agent": from . import release names = [m.name for m in registry.agent_modules()] diff --git a/src/sdlc_hub/core/paths.py b/src/sdlc_hub/core/paths.py index 10ea4d1..59fb68f 100644 --- a/src/sdlc_hub/core/paths.py +++ b/src/sdlc_hub/core/paths.py @@ -10,6 +10,27 @@ def home() -> Path: return Path(os.environ.get("SDLC_HUB_HOME", str(Path.home() / ".local" / "share" / "sdlc-hub"))) +def legacy_home() -> Path: + """Каталог данных под прежним именем (до переименования в sdlc-hub).""" + return Path.home() / ".local" / "share" / "jira-hub" + + +def home_problem() -> "str | None": + """Текст отказа, если данные лежат только под прежним именем; при заданном SDLC_HUB_HOME проверки нет.""" + if os.environ.get("SDLC_HUB_HOME") or home().exists() or not legacy_home().exists(): + return None + return ("каталог данных хаба лежит под прежним именем ~/.local/share/jira-hub; перенесите его: " + "bash scripts/cutover.sh (или mv ~/.local/share/jira-hub ~/.local/share/sdlc-hub при остановленном хабе)") + + +def home_warning() -> "str | None": + """Предупреждение, если существуют оба каталога (SDLC_HUB_HOME не задан).""" + if os.environ.get("SDLC_HUB_HOME") or not (home().exists() and legacy_home().exists()): + return None + return ("есть и ~/.local/share/sdlc-hub, и ~/.local/share/jira-hub: работаю с новым, " + "прежний каталог ~/.local/share/jira-hub можно удалить после проверки") + + def db_path() -> Path: return home() / "hub.sqlite" diff --git a/tests/conftest.py b/tests/conftest.py index 55ca5a5..d3ac10d 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -13,6 +13,12 @@ from sdlc_hub.modules.jira import project_fields from sdlc_hub.modules.jira import projects as jira_projects +@pytest.fixture(autouse=True) +def _isolated_home(monkeypatch, tmp_path_factory): + """HOME — во временный каталог: на результат не влияют реальные ~/.local/share/{jira,sdlc}-hub разработчика.""" + monkeypatch.setenv("HOME", str(tmp_path_factory.mktemp("home"))) + + @pytest.fixture def open_db(): """Фабрика соединений: open_db() — база в памяти, open_db(path) — файл. Все закрываются после теста.""" diff --git a/tests/test_cli.py b/tests/test_cli.py index 51bdcf1..168f641 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -2,6 +2,8 @@ import json import os from pathlib import Path +import pytest + from sdlc_hub.core import api, cli, paths, release, settings FIXTURE = Path(__file__).parent / "fixtures" / "config.example.json" @@ -103,3 +105,34 @@ def test_agent_publish_migrates_old_layout(tmp_path, monkeypatch, capsys): assert cli.main(["agent", "publish", str(new)]) == 1 # v13 < v14: «только вверх» видит прежнюю публикацию assert not (rel / "agent.py").exists() and not (rel / "manifest.json").exists() assert json.loads((rel / "jira" / "manifest.json").read_text(encoding="utf-8"))["version"] == 14 + + +def _homes(monkeypatch, tmp_path, legacy=True, new=False): + monkeypatch.delenv("SDLC_HUB_HOME", raising=False) + monkeypatch.setenv("HOME", str(tmp_path)) + share = tmp_path / ".local" / "share" + if legacy: (share / "jira-hub").mkdir(parents=True) + if new: (share / "sdlc-hub").mkdir(parents=True) + return share + + +@pytest.mark.parametrize("argv", [["serve"], ["tokens"], ["mcp", "jira"], ["agent", "publish", "x.py"]]) +def test_legacy_home_refused(monkeypatch, tmp_path, capsys, argv): + share = _homes(monkeypatch, tmp_path) + assert cli.main(argv) == 2 + err = capsys.readouterr().err + assert "~/.local/share/jira-hub" in err and "scripts/cutover.sh" in err + assert not (share / "sdlc-hub").exists() + assert sorted(p.name for p in (share / "jira-hub").iterdir()) == [] + + +def test_both_homes_warn(monkeypatch, tmp_path, capsys): + _homes(monkeypatch, tmp_path, new=True) + cli.main(["tokens"]) + assert "прежний каталог ~/.local/share/jira-hub можно удалить" in capsys.readouterr().err + + +def test_env_home_skips_legacy_check(monkeypatch, tmp_path, capsys): + _homes(monkeypatch, tmp_path) + monkeypatch.setenv("SDLC_HUB_HOME", str(tmp_path / "x")) + assert cli.main(["tokens"]) == 0