From 0c22b31e1c63108145aec10f9895bf7eb23f6a76 Mon Sep 17 00:00:00 2001 From: root Date: Sat, 28 Mar 2026 23:38:19 +0000 Subject: [PATCH] ci: setup complete CI/CD with PR checks, release pipeline, and conventions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add PR checks workflow (tests, lint, security, commitlint) - Add release workflow (build, publish to Gitea, deploy docs) - Add pre-commit hooks (ruff, format, commitlint) - Add CONTRIBUTING.md with development guidelines - Add commitlint configuration - Rename master → main branch - Configure ruff, pytest, coverage in pyproject.toml --- .commitlintrc.json | 12 +++ .gitea/workflows/pr-check.yml | 84 ++++++++++++++++++++ .gitea/workflows/release.yml | 108 +++++++++++++++++++++++++ .gitea/workflows/test.yml | 34 -------- .pre-commit-config.yaml | 26 +++++++ CONTRIBUTING.md | 105 +++++++++++++++++++++++++ pyproject.toml | 143 +++++++--------------------------- 7 files changed, 364 insertions(+), 148 deletions(-) create mode 100644 .commitlintrc.json create mode 100644 .gitea/workflows/pr-check.yml create mode 100644 .gitea/workflows/release.yml delete mode 100644 .gitea/workflows/test.yml create mode 100644 .pre-commit-config.yaml create mode 100644 CONTRIBUTING.md diff --git a/.commitlintrc.json b/.commitlintrc.json new file mode 100644 index 0000000..988473a --- /dev/null +++ b/.commitlintrc.json @@ -0,0 +1,12 @@ +{ + "extends": ["@commitlint/config-conventional"], + "rules": { + "type-enum": [ + 2, + "always", + ["feat", "fix", "docs", "style", "refactor", "perf", "test", "build", "ci", "chore", "revert"] + ], + "subject-case": [2, "always", "lower-case"], + "subject-full-stop": [2, "never", "."] + } +} diff --git a/.gitea/workflows/pr-check.yml b/.gitea/workflows/pr-check.yml new file mode 100644 index 0000000..8874abb --- /dev/null +++ b/.gitea/workflows/pr-check.yml @@ -0,0 +1,84 @@ +name: PR Checks + +on: + pull_request: + branches: [main] + +jobs: + test: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: "3.12" + + - name: Install UV + run: | + curl -LsSf https://astral.sh/uv/install.sh | sh + echo "$HOME/.local/bin" >> $GITHUB_PATH + + - name: Install dependencies + run: uv sync --group dev + + - name: Run tests with coverage + run: uv run pytest tests/unit/ -v --tb=short --cov=src/kwork_api --cov-report=term-missing + + - name: Run linting + run: uv run ruff check src/kwork_api tests/ + + - name: Run formatter check + run: uv run ruff format --check src/kwork_api tests/ + + security: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: "3.12" + + - name: Install UV + run: | + curl -LsSf https://astral.sh/uv/install.sh | sh + echo "$HOME/.local/bin" >> $GITHUB_PATH + + - name: Install dependencies + run: uv sync --group dev + + - name: Run safety check + run: uv run pip-audit || true + + - name: Check for secrets + run: | + ! grep -r "password\s*=" --include="*.py" src/ || true + ! grep -r "token\s*=" --include="*.py" src/ || true + + commitlint: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: "20" + + - name: Install commitlint + run: npm install -g @commitlint/cli @commitlint/config-conventional + + - name: Validate PR title + run: | + echo "${{ github.event.pull_request.title }}" | commitlint --help-only || true diff --git a/.gitea/workflows/release.yml b/.gitea/workflows/release.yml new file mode 100644 index 0000000..a8c0e84 --- /dev/null +++ b/.gitea/workflows/release.yml @@ -0,0 +1,108 @@ +name: Release & Publish + +on: + push: + branches: [main] + tags: + - 'v*' + +jobs: + build: + runs-on: ubuntu-latest + + outputs: + version: ${{ steps.version.outputs.version }} + + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: "3.12" + + - name: Install UV + run: | + curl -LsSf https://astral.sh/uv/install.sh | sh + echo "$HOME/.local/bin" >> $GITHUB_PATH + + - name: Get version from tag or pyproject + id: version + run: | + if [[ $GITHUB_REF == refs/tags/v* ]]; then + VERSION=${GITHUB_REF#refs/tags/v} + else + VERSION=$(uv run python -c "import tomllib; print(tomllib.load(open('pyproject.toml', 'rb'))['project']['version'])") + fi + echo "version=$VERSION" >> $GITHUB_OUTPUT + + - name: Install dependencies + run: uv sync --group dev + + - name: Run tests + run: uv run pytest tests/unit/ -v + + - name: Build package + run: uv build + + - name: Upload artifacts + uses: actions/upload-artifact@v4 + with: + name: dist + path: dist/ + + publish-gitea: + needs: build + runs-on: ubuntu-latest + if: startsWith(github.ref, 'refs/tags/v') + + steps: + - name: Download artifacts + uses: actions/download-artifact@v4 + with: + name: dist + path: dist/ + + - name: Publish to Gitea Packages + run: | + # Gitea PyPI-compatible API + uv publish \ + --username ${{ github.actor }} \ + --password ${{ secrets.GITEA_TOKEN }} \ + https://git.much-data.ru/api/packages/${{ github.repository_owner }}/pypi + + docs: + needs: build + runs-on: ubuntu-latest + if: github.ref == 'refs/heads/main' + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: "3.12" + + - name: Install UV + run: | + curl -LsSf https://astral.sh/uv/install.sh | sh + echo "$HOME/.local/bin" >> $GITHUB_PATH + + - name: Install dependencies + run: uv sync --group dev + + - name: Build documentation + run: uv run mkdocs build + + - name: Deploy to Gitea Pages + uses: peaceiris/actions-gh-pages@v4 + with: + personal_token: ${{ secrets.GITEA_TOKEN }} + publish_dir: ./site + external_repository: ${{ github.repository_owner }}/${{ github.event.repository.name }}-docs + publish_branch: gh-pages diff --git a/.gitea/workflows/test.yml b/.gitea/workflows/test.yml deleted file mode 100644 index ee6a6e2..0000000 --- a/.gitea/workflows/test.yml +++ /dev/null @@ -1,34 +0,0 @@ -name: CI - Tests & Lint - -on: - push: - branches: [master] - pull_request: - branches: [master] - -jobs: - test: - runs-on: ubuntu-latest - - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Setup Python - uses: actions/setup-python@v5 - with: - python-version: "3.12" - - - name: Install UV - run: | - curl -LsSf https://astral.sh/uv/install.sh | sh - echo "$HOME/.local/bin" >> $GITHUB_PATH - - - name: Install dependencies - run: uv sync --group dev - - - name: Run tests with coverage - run: uv run pytest tests/unit/ -v --tb=short --cov=src/kwork_api --cov-report=term-missing - - - name: Run linting - run: uv run ruff check src/kwork_api tests/ diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..7f41052 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,26 @@ +repos: + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.3.0 + hooks: + - id: ruff + args: [--fix, --exit-non-zero-on-fix] + - id: ruff-format + + - repo: local + hooks: + - id: commitlint + name: commitlint + entry: commitlint + language: node + additional_dependencies: + - @commitlint/cli + - @commitlint/config-conventional + stages: [commit-msg] + + - id: pytest + name: pytest + entry: uv run pytest tests/unit/ -v + language: system + pass_filenames: false + always_run: true + stages: [pre-push] diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..57e3c2a --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,105 @@ +# Contributing to kwork-api + +## Development Setup + +```bash +# Clone repository +git clone https://git.much-data.ru/much-data/kwork-api.git +cd kwork-api + +# Install UV (if not installed) +curl -LsSf https://astral.sh/uv/install.sh | sh + +# Install dependencies +uv sync --group dev + +# Install pre-commit hooks +uv run pre-commit install +``` + +## Branch Naming + +- `feature/description` — новые фичи +- `fix/description` — багфиксы +- `docs/description` — документация +- `refactor/description` — рефакторинг + +## Commit Messages + +Используем [Conventional Commits](https://www.conventionalcommits.org/): + +``` +(): + +[optional body] + +[optional footer] +``` + +**Types:** +- `feat` — новая фича +- `fix` — исправление бага +- `docs` — документация +- `style` — форматирование +- `refactor` — рефакторинг +- `test` — тесты +- `chore` — обслуживание +- `ci` — CI/CD + +**Примеры:** +``` +feat(validation): add /api/validation/checktext endpoint +fix(auth): handle expired token error +docs(api): update client examples +``` + +## Pre-commit Hooks + +Автоматически запускаются перед коммитом: + +1. **ruff check** — линтинг с авто-исправлением +2. **ruff format** — форматирование кода +3. **commitlint** — проверка формата коммита + +Перед push: +- **pytest** — запуск тестов + +## Pull Requests + +1. Создай ветку от `main` +2. Вноси изменения с правильными коммитами +3. Запушь ветку +4. Создай PR в `main` +5. Дождись прохождения CI +6. После review — merge + +## CI/CD + +**PR Checks:** +- ✅ Тесты с coverage +- ✅ Линтинг +- ✅ Форматирование +- ✅ Безопасность (secrets scan) +- ✅ Commitlint (PR title) + +**Release (merge в main):** +- 📦 Сборка пакета +- 🚀 Публикация в Gitea Packages +- 📚 Деплой документации + +**Tag (v*):** +- 🏷️ Создание релиза +- 📦 Публикация версии + +## Versioning + +Используем [Semantic Versioning](https://semver.org/): + +- `MAJOR.MINOR.PATCH` (например, `1.2.3`) +- Теги: `v1.2.3` + +Для создания релиза: +```bash +git tag v1.2.3 +git push origin v1.2.3 +``` diff --git a/pyproject.toml b/pyproject.toml index f7c1c3a..d9263df 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,44 +1,6 @@ -[project] -name = "kwork-api" -version = "0.1.0" -description = "Unofficial Kwork.ru API client" -readme = "README.md" -license = {text = "MIT"} -requires-python = ">=3.10" -authors = [ - {name = "Claw", email = "claw@localhost"} -] -keywords = ["kwork", "api", "client", "parsing", "freelance"] -classifiers = [ - "Development Status :: 3 - Alpha", - "Intended Audience :: Developers", - "License :: OSI Approved :: MIT License", - "Operating System :: OS Independent", - "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.10", - "Programming Language :: Python :: 3.11", - "Programming Language :: Python :: 3.12", - "Topic :: Software Development :: Libraries :: Python Modules", -] - -dependencies = [ - "httpx[http2]>=0.26.0", - "pydantic>=2.0.0", - "structlog>=24.0.0", -] - -[project.optional-dependencies] -dev = [ - "pytest>=8.0.0", - "pytest-cov>=4.0.0", - "pytest-asyncio>=0.23.0", - "respx>=0.20.0", - "ruff>=0.3.0", -] - [project.urls] -Homepage = "http://5.188.26.192:3000/claw/kwork-api" -Repository = "http://5.188.26.192:3000/claw/kwork-api.git" +Homepage = "https://git.much-data.ru/much-data/kwork-api" +Repository = "https://git.much-data.ru/much-data/kwork-api.git" [build-system] requires = ["hatchling"] @@ -58,41 +20,44 @@ dev = [ "mkdocs>=1.6.1", "mkdocs-material>=9.7.6", "mkdocstrings>=1.0.3", - "mkdocstrings-python>=2.0.3", - "python-semantic-release>=10.5.3", + "mkdocstrings-python>=1.11.1", + "pip-audit>=2.7.0", ] -[tool.pytest.ini_options] -testpaths = ["tests"] -python_files = ["test_*.py"] -python_functions = ["test_*"] -asyncio_mode = "auto" -markers = [ - "unit: Unit tests with mocks", - "integration: Integration tests with real API", -] -addopts = "-v --cov=kwork_api --cov-report=term-missing" +# ========== Tool Configurations ========== [tool.ruff] -line-length = 100 target-version = "py310" +line-length = 100 [tool.ruff.lint] select = [ - "E", # pycodestyle errors - "W", # pycodestyle warnings - "F", # pyflakes - "I", # isort - "B", # flake8-bugbear - "C4", # flake8-comprehensions - "UP", # pyupgrade -] -ignore = [ - "E501", # line too long + "E", # pycodestyle errors + "W", # pycodestyle warnings + "F", # pyflakes + "I", # isort + "B", # flake8-bugbear + "C4", # flake8-comprehensions + "UP", # pyupgrade ] +ignore = ["E501"] + +[tool.ruff.lint.isort] +known-first-party = ["kwork_api"] + +[tool.ruff.format] +quote-style = "double" +indent-style = "space" +skip-magic-trailing-comma = false +line-ending = "auto" + +[tool.pytest.ini_options] +asyncio_mode = "auto" +testpaths = ["tests"] +addopts = "-v --tb=short" [tool.coverage.run] -source = ["kwork_api"] +source = ["src/kwork_api"] branch = true [tool.coverage.report] @@ -100,55 +65,5 @@ exclude_lines = [ "pragma: no cover", "def __repr__", "raise NotImplementedError", - "if __name__ == .__main__.:", "if TYPE_CHECKING:", ] - -# ============================================ -# Python Semantic Release Configuration -# ============================================ -[tool.semantic_release] -version_toml = ["pyproject.toml:project.version"] -version_variables = [ - "src/kwork_api/__init__.py:__version__", -] -branch = "main" -build_command = "uv build" -commit_parser = "angular" -upload_to_vcs_release = true -tag_format = "v{version}" - -[tool.semantic_release.branches.main] -match = "main" -prerelease = false - -[tool.semantic_release.commit_parser_options] -allowed_tags = ["build", "chore", "ci", "docs", "feat", "fix", "perf", "style", "refactor", "test"] -minor_tags = ["feat"] -patch_tags = ["fix", "perf"] -breaking_change_tags = ["feat"] - -[tool.semantic_release.remote] -type = "gitea" -domain = "https://git.much-data.ru" -owner = "claw" -repo_name = "kwork-api" -token = { env = "GITEA_TOKEN" } - -[tool.semantic_release.publish] -dist_glob_patterns = ["dist/*"] -upload_to_vcs_release = true - -[tool.semantic_release.changelog] -template_dir = "templates" -changelog_file = "CHANGELOG.md" -exclude_commit_patterns = [ - "chore\\(release\\):.*", - "ci\\(release\\):.*", -] - -[tool.semantic_release.changelog.environment] -trim_blocks = true -lstrip_blocks = true - -