feat(infra): шаблон репозитория загрузок tgit-uploads — CI/CD, служебные скрипты, демо-сервис

- .sourcecraft/ci.yaml: push в main → check (compile · ruff мягкий · bandit · pytest по сервисам) → deploy
- .tgit/changed_services.py: изменённые services/<name>/ (без родителя — все сервисы)
- .tgit/run_tests.py: pytest каждого сервиса со своими requirements.txt
- deploy пока заглушка со списком сервисов — tgit-runner в #13
- services/hello: демо-сервис с тестом; tgit.yaml — формат настроек сервиса

Refs #7

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-13 23:59:41 +03:00
co-authored by Claude Opus 5.5
parent bbd4ac5bc1
commit d4dba9748d
10 changed files with 200 additions and 2 deletions
+34
View File
@@ -0,0 +1,34 @@
"""Run pytest for every service that has tests, each in its own env with its own requirements.txt."""
import subprocess
import sys
from pathlib import Path
def has_tests(service: Path) -> bool:
return any(service.rglob("test_*.py")) or any(service.rglob("*_test.py"))
def main() -> int:
failed: list[str] = []
services = sorted(p for p in Path("services").glob("*/") if p.is_dir()) if Path("services").is_dir() else []
for service in services:
if not has_tests(service):
print(f"[{service.name}] no tests — skipped")
continue
cmd = ["uv", "run", "--no-project", "--with", "pytest"]
req = service / "requirements.txt"
if req.is_file():
cmd += ["--with-requirements", str(req)]
cmd += ["pytest", "-q", str(service)]
print(f"[{service.name}] {' '.join(cmd)}", flush=True)
if subprocess.run(cmd).returncode != 0:
failed.append(service.name)
if failed:
print(f"tests failed: {', '.join(failed)}")
return 1
return 0
if __name__ == "__main__":
sys.exit(main())