diff --git a/.cursor/rules/engineering.mdc b/.cursor/rules/engineering.mdc index 3c6d469..68100ec 100644 --- a/.cursor/rules/engineering.mdc +++ b/.cursor/rules/engineering.mdc @@ -15,7 +15,7 @@ alwaysApply: true **ARCH-01** | MUST | Новая persistence-логика — метод `store.Backend` + реализации в `repository` и `store.Memory`; SQL не в `httpapi`. *Rationale:* единая абстракция данных. -*Проверка:* grep SQL в `internal/httpapi` — отсутствие; review. +*Проверка:* CI `scripts/lint-httpapi.sh`; grep SQL в `internal/httpapi` — отсутствие. **ARCH-02** | MUST | HTTP-маршруты только в `internal/httpapi`; регистрация через `http.ServeMux` с паттернами `METHOD /v1/...`. *Rationale:* один слой REST. @@ -75,7 +75,7 @@ alwaysApply: true **STYLE-05** | MUST | HTTP-ошибки — `writeProblem` / `writeJSON` (`application/problem+json` для 4xx/5xx). *Rationale:* RFC 9457, OpenAPI. -*Проверка:* `problem.go`. +*Проверка:* `problem.go`; CI `scripts/lint-httpapi.sh` (5xx и 4xx store/cdn/csv). **STYLE-06** | MUST | JSON полей HTTP DTO согласованы с `docs/openapi.yaml`. *Rationale:* контракт API. @@ -123,7 +123,7 @@ alwaysApply: true **TEST-03** | MUST | Новые BIRD-сценарии в `internal/birdfmt/testdata/scenarios/*/bird.conf` + `bird -p`. *Проверка:* CI job `bird2`. -**TEST-04** | MUST | Изменения `web/` — локально `npm run check` и `npm run lint` (CI web пока не в scope). +**TEST-04** | MUST | Изменения `web/` — локально `npm run check` и `npm run lint`; CI job `web` в `.gitea/workflows/ci.yaml`. *Проверка:* локальные команды. **TEST-05** | MUST | Изменения OpenAPI — `npx @redocly/cli lint docs/openapi.yaml`. @@ -234,7 +234,9 @@ npx @redocly/cli lint docs/openapi.yaml # birdfmt: go test ./internal/birdfmt/... -count=1 ``` -**Рекомендуется (частично внедрено):** CI job `web` (Gitea); `scripts/lint-httpapi.sh` в job `go`; `.golangci.yml` (локально); pre-commit gofmt/prettier. +**CI (Gitea):** job `web` (check + lint); job `go`: `go vet`, `scripts/lint-httpapi.sh` (ARCH-01, ERR-01), `scripts/check-migrations-pair.sh` (DEP-03), `golangci-lint`, `go test -race`, build `cmd/*`. + +**Рекомендуется локально:** `.golangci.yml`; `.pre-commit-config.yaml` (gofmt + prettier web). **Только code review:** слои SQL; роли; idempotency; OpenAPI bodies; secrets в compose. diff --git a/.gitea/workflows/ci.yaml b/.gitea/workflows/ci.yaml index c1b5fa5..1074aa7 100644 --- a/.gitea/workflows/ci.yaml +++ b/.gitea/workflows/ci.yaml @@ -88,6 +88,7 @@ jobs: deploy/docker/evobgp-web/*) docker_web=true ;; deploy/docker/bird2/*) docker_bird=true ;; go.mod|go.sum|go.work) go=true ;; + migrations/*) go=true ;; *.go) go=true ;; cmd/*|internal/*) go=true ;; esac @@ -157,6 +158,12 @@ jobs: run: go vet ./... - name: Lint httpapi (ERR-01 / ARCH-01) run: sh scripts/lint-httpapi.sh + - name: Check migration pairs (DEP-03) + run: sh scripts/check-migrations-pair.sh + - name: golangci-lint + uses: golangci/golangci-lint-action@v6 + with: + version: v1.62 - name: Test run: go test ./... -race -count=1 - name: Build all commands diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..e2a72ee --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,19 @@ +# Local hooks (optional): install with `pre-commit install` +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v5.0.0 + hooks: + - id: trailing-whitespace + - id: end-of-file-fixer + - repo: https://github.com/dnephin/pre-commit-golang + rev: v0.5.1 + hooks: + - id: go-fmt + - repo: local + hooks: + - id: prettier-web + name: prettier (web) + entry: bash -c 'cd web && npx prettier --check .' + language: system + files: ^web/ + pass_filenames: false diff --git a/scripts/check-migrations-pair.sh b/scripts/check-migrations-pair.sh new file mode 100644 index 0000000..db34646 --- /dev/null +++ b/scripts/check-migrations-pair.sh @@ -0,0 +1,41 @@ +#!/usr/bin/env sh +# DEP-03: postgres and sqlite migration sets must have matching numbered pairs. +set -eu + +ROOT="$(CDPATH= cd -- "$(dirname "$0")/.." && pwd)" +PG="$ROOT/migrations/postgres" +SQL="$ROOT/migrations/sqlite" + +list_nums() { + dir="$1" + ls "$dir" 2>/dev/null | sed -n 's/^\([0-9]\{6\}\)_.*\.up\.sql$/\1/p' | sort -u +} + +pg_nums="$(list_nums "$PG")" +sql_nums="$(list_nums "$SQL")" + +if [ "$pg_nums" != "$sql_nums" ]; then + echo "check-migrations-pair: postgres and sqlite migration numbers differ" >&2 + echo "postgres only:" >&2 + comm -23 <(printf '%s\n' "$pg_nums") <(printf '%s\n' "$sql_nums") >&2 || true + echo "sqlite only:" >&2 + comm -13 <(printf '%s\n' "$pg_nums") <(printf '%s\n' "$sql_nums") >&2 || true + exit 1 +fi + +for n in $pg_nums; do + pg_up="$(ls "$PG"/${n}_*.up.sql 2>/dev/null | head -1)" + sql_up="$(ls "$SQL"/${n}_*.up.sql 2>/dev/null | head -1)" + if [ -z "$pg_up" ] || [ -z "$sql_up" ]; then + echo "check-migrations-pair: missing .up.sql for $n" >&2 + exit 1 + fi + pg_base="$(basename "$pg_up" .up.sql)" + sql_base="$(basename "$sql_up" .up.sql)" + if [ "$pg_base" != "$sql_base" ]; then + echo "check-migrations-pair: name mismatch for $n: $pg_base vs $sql_base" >&2 + exit 1 + fi +done + +echo "check-migrations-pair: ok ($PG and $SQL)"