name: CI Trade-In # Forgejo Actions pre-merge gate for the SUBPROJECT tradein-mvp/. # WHY THIS FILE EXISTS (#2208): основной .forgejo/workflows/ci.yml гейтит # ТОЛЬКО backend/** + frontend/** главного стека — tradein-PR проходили на # пусто-зелёных чеках (paths-filter no-op), а pytest tradein жил лишь в # post-merge deploy-tradein.yml. Итог: сломанный tradein-код мержился в main # и обнаруживался только на деплое. Этот workflow добавляет РЕАЛЬНЫЙ pre-merge # gate: tradein-backend pytest + tradein-frontend type-check/lint ДО мержа. on: # ТОЛЬКО pull_request — НЕТ push-триггера на feature-ветки (CI-шторм #1709, # см. подробное обоснование в ci.yml). Кратко: раньше push+pull_request на один # SHA давали разный github.ref → разные concurrency-группы → 2× прогон на # дефицитных раннерах. В bot-пайплайне каждый коммит идёт через PR, так что # pull_request гейтит его полностью; push-прогон был чистым дублем. pull_request: branches: [main] concurrency: # github.ref стабилен на весь PR (refs/pull//merge) → новый push в ветку PR # отменяет предыдущий незавершённый прогон ЭТОГО PR вместо накопления. group: ci-tradein-${{ github.ref }} cancel-in-progress: true jobs: # Paths-filter: гейт бежит ТОЛЬКО когда поменялся tradein-код. # PR не трогающий tradein-mvp/ → оба job'а no-op'ятся → дёшево. changes: runs-on: ubuntu-latest outputs: backend: ${{ steps.filter.outputs.backend }} frontend: ${{ steps.filter.outputs.frontend }} steps: - uses: actions/checkout@v4 - uses: dorny/paths-filter@v3 id: filter with: filters: | backend: - 'tradein-mvp/backend/**' - 'tradein-mvp/packages/**' # workspace root: lock-only bump (uv lock --upgrade) или правка # [tool.uv.workspace] меняют реальные зависимости → гейт обязан бежать. - 'tradein-mvp/uv.lock' - 'tradein-mvp/pyproject.toml' - '.forgejo/workflows/ci-tradein.yml' frontend: - 'tradein-mvp/frontend/**' - '.forgejo/workflows/ci-tradein.yml' backend-tests: runs-on: ubuntu-latest needs: changes if: needs.changes.outputs.backend == 'true' defaults: run: working-directory: ./tradein-mvp/backend env: # psycopg v3 требует parseable URL на импорте; реального коннекта нет — # DB-тесты мокаются (mirror deploy-tradein.yml test-job). DATABASE_URL: postgresql+psycopg://test:test@localhost:5432/test steps: - uses: actions/checkout@v4 - name: Install uv # Официальный standalone-инсталлер. НЕ astral-sh/setup-uv — он ломается # на Forgejo-runner с PEP 668 externally-managed-environment (#666 CI). run: | curl -LsSf https://astral.sh/uv/install.sh | sh echo "$HOME/.local/bin" >> "$GITHUB_PATH" - name: Cache uv packages # Кросс-прогонный кэш скачанных/собранных wheel'ов (~/.cache/uv). # continue-on-error чтобы сбой cache-бэкенда раннера НИКОГДА не ронял gate. # Ключ по workspace-локу tradein-mvp/uv.lock (tracked с воркспейса #2137). uses: actions/cache@v4 continue-on-error: true with: path: ~/.cache/uv key: uv-tradein-${{ runner.os }}-${{ hashFiles('tradein-mvp/uv.lock') }} restore-keys: | uv-tradein-${{ runner.os }}- - name: Sync deps (incl. dev group — pytest) # Workspace-лок tradein-mvp/uv.lock TRACKED (с воркспейса #2137; gitignored # только старый backend/uv.lock) → --frozen детерминирован и зеркалит # Dockerfile (uv sync --frozen --no-dev там). uv находит workspace root # вверх от cwd. run: uv sync --frozen - name: Run pytest (tradein-mvp/backend) # DESELECT (актуализировано 2026-07-02, #2208): test_search_cache_hit падает # ТОЛЬКО в whole-suite ordering (401 vs 200; в изоляции проходит) — global-state # leak из другого test-модуля, pre-existing. Второй исторический deselect # (test_cian_valuation::test_cache_hit_returns_cached) убран — проходит в # полном прогоне (проверено локально: 2947 passed / 1 failed). Список обязан # совпадать с test-job в deploy-tradein.yml. run: | uv run pytest -q \ --deselect "tests/test_search_api.py::test_search_cache_hit" frontend-checks: runs-on: ubuntu-latest needs: changes if: needs.changes.outputs.frontend == 'true' defaults: run: working-directory: ./tradein-mvp/frontend steps: - uses: actions/checkout@v4 - name: Set up Node # Node 20 — major из tradein-mvp/frontend/Dockerfile (node:20-alpine). # npm-кэш setup-node НЕ настраиваем: в tradein-mvp/frontend нет # package-lock.json (Dockerfile ставит через npm install), а cache=npm # без lockfile падает. Кэш wheels/node тут не критичен для type-check/lint. uses: actions/setup-node@v4 with: node-version: "20" - name: Install deps (npm install, no lockfile) # ТОЧНЫЕ флаги из tradein-mvp/frontend/Dockerfile (deps stage): # --legacy-peer-deps — Tailwind/React 19 peer-dep mismatches; # --no-audit --no-fund — тише и быстрее в CI. `install` (не `ci`): # в tradein-mvp/frontend НЕТ package-lock.json (есть pnpm-lock.yaml, но # Dockerfile ставит именно npm install) → `npm ci` упал бы. run: npm install --legacy-peer-deps --no-audit --no-fund - name: Type-check (tsc --noEmit) # Blocking: любая TS-ошибка → job RED. run: npm run type-check - name: Lint (next lint) # Blocking: любая ESLint-ошибка → job RED. run: npm run lint