Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .githooks/pre-push
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#!/usr/bin/env bash
# Git: core.hooksPath 가 이 디렉터리를 가리킬 때 사용됩니다.
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
exec bash "$ROOT/scripts/git-pre-push.sh"
2 changes: 2 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -400,6 +400,8 @@ bun run dev:api-server # Rust API (cargo watch)
bun run dev:ai-server # Python AI server (uv)
bun run build # Production build (via Turborepo)
bun run lint # ESLint (and package scripts where configured)
bun run ci:local # 로컬 CI (pre-push 와 동일: 프론트 슬롯 + api-server; ai-server 는 RUN_AI_SERVER_CI=1 일 때만)
# 훅: 저장소 루트에서 `just hook` → core.hooksPath=.githooks

# Split local dev: Meilisearch·Redis·SearXNG = Docker first, then:
bun run dev:local-deps # Docker deps only (see scripts/local-deps-up.sh)
Expand Down
10 changes: 10 additions & 0 deletions Justfile
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,14 @@ local-deps:
local-deps-down:
bash "{{ repo }}/scripts/local-deps-down.sh"

# Git pre-push — 모노레포 로컬 CI (프론트 슬롯 + ai-server + api-server)
hook:
#!/usr/bin/env bash
set -euo pipefail
chmod +x "{{ repo }}/scripts/git-pre-push.sh" "{{ repo }}/.githooks/pre-push" "{{ repo }}/packages/ai-server/scripts/pre-push.sh"
git -C "{{ repo }}" config core.hooksPath .githooks
echo "OK: git config core.hooksPath=.githooks (repo: {{ repo }})"

# 온보딩용 안내
local-help:
@echo "0) Env: .env.dev + .dev.env (see .env.dev.example / .dev.env.example in each package)"
Expand All @@ -32,3 +40,5 @@ local-help:
@echo "3) 터미널 D: just local-fe"
@echo " local-be 종료: 터미널 A에서 Ctrl+C"
@echo "전체 한 터미널: bun run dev (turbo, 로그 한 스트림)"
@echo "push 전 로컬 CI: just hook 후 bun run ci:local (또는 git push 가 훅 실행)"

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@
"build:api-server": "cd packages/api-server && cargo build --release",
"build:backend": "bun run build:api-server",
"lint": "turbo run lint",
"test": "turbo run test"
"test": "turbo run test",
"ci:local": "bash scripts/git-pre-push.sh"
},
"devDependencies": {
"@types/react": "^19.2.0",
Expand Down
5 changes: 4 additions & 1 deletion packages/ai-server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
"dev": "uv run python -m src.main",
"test": "uv run pytest",
"lint": "uv run flake8 src",
"format": "uv run black ."
"format": "uv run black .",
"format:check": "uv run black --check src tests",
"test:ci": "uv run pytest -m \"not integration and not e2e and not performance and not external_api and not requires_redis and not requires_network\"",
"ci:local": "bash scripts/pre-push.sh"
}
}
28 changes: 28 additions & 0 deletions packages/ai-server/scripts/pre-push.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
#!/usr/bin/env bash
# ai-server 로컬 CI — 루트 scripts/git-pre-push.sh 에서 호출.
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
cd "$SCRIPT_DIR/.."

if ! command -v uv >/dev/null 2>&1; then
echo "error: uv 가 필요합니다. https://docs.astral.sh/uv/" >&2
exit 1
fi

echo "=== ai-server: flake8 ==="
uv run flake8 src

echo "=== ai-server: black --check ==="
uv run black --check src tests

echo "=== ai-server: pytest (CI 서브집합) ==="
set +e
uv run pytest -m "not integration and not e2e and not performance and not external_api and not requires_redis and not requires_network"
_py_ec=$?
set -e
# 5 = 수집된 테스트 없음 — 마커만 맞춰 두었을 때 허용
if [[ "$_py_ec" -ne 0 && "$_py_ec" -ne 5 ]]; then
exit "$_py_ec"
fi

echo "=== ai-server: 체크 통과 ==="
2 changes: 1 addition & 1 deletion packages/api-server/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@

## 품질 게이트 (push 전)

`backend/scripts/pre-push.sh` — 포맷, `cargo clippy --all-targets -- -D warnings`, **`cargo test --lib`** (단위만), **`cargo-deny`**(`cargo install cargo-deny`), **`cargo-tarpaulin`**(라인 커버리지 **10%** 미만 시 실패; `lib`, `src/entities/*` 제외; `cargo install cargo-tarpaulin`), `check-migration-sync.sh` (**`DATABASE_URL` + `psql` 필수**; 비어 있으면 `backend/.env` → `.env.dev` 순으로 로드). 실 DB 통합 테스트는 [`scripts/run-integration-tests.sh`](scripts/run-integration-tests.sh). Git 훅으로 쓰면 **원격 `main`/`master` 직접 push 차단** — [docs/GIT_WORKFLOW.md](docs/GIT_WORKFLOW.md). **원격 GitHub Actions CI는 사용하지 않음** — 품질 게이트는 pre-push가 담당합니다. **`git push --no-verify`는 사용 금지.**
모노레포 루트 [`scripts/git-pre-push.sh`](../../scripts/git-pre-push.sh)(`bun run ci:local`)가 **web 슬롯 → (선택) ai-server → api-server** 순으로 로컬 CI를 돌립니다. **ai-server** 단계는 기본 건너뜀 — 켜기: `RUN_AI_SERVER_CI=1`(스크립트·`uv`는 [packages/ai-server/scripts/pre-push.sh](../ai-server/scripts/pre-push.sh)). Rust(api-server) 단계는 [`scripts/pre-push.sh`](scripts/pre-push.sh) — 포맷, `cargo clippy`, **`cargo test --lib`**, **`cargo-deny`**, **`cargo-tarpaulin`**, `check-migration-sync.sh` (**`DATABASE_URL` + `psql` 필수**; 비어 있으면 `packages/api-server/.env` → `.env.dev` 순으로 로드). 실 DB 통합 테스트는 [`scripts/run-integration-tests.sh`](scripts/run-integration-tests.sh). 훅 설치는 [docs/GIT_WORKFLOW.md](docs/GIT_WORKFLOW.md)(`core.hooksPath=.githooks`). **원격 GitHub Actions CI는 사용하지 않음.** **`git push --no-verify`는 사용 금지.**

- `Cargo.toml` `[lints.rust]`: **`unused_imports = "deny"`** — 사용하지 않는 `use`는 `cargo build` / `cargo clippy`에서 오류 (pre-push와 동일).
- `clippy.toml`(API 루트): `println!`/`dbg!`/`eprintln!` 금지, **런타임(API) 코드에서 `Option::unwrap` / `Result::unwrap` 금지** (테스트에서는 `unwrap` 허용). 테스트는 `#[cfg(test)]` 직하위 `mod`에 붙인 `#[allow(clippy::disallowed_methods)]`, `lib.rs`의 `pub mod tests`, 통합 테스트 `tests/integration_*.rs`의 `#![allow(...)]`로 예외 처리. SeaORM `DeriveEntityModel`·`serde_json::json!` 등 매크로 전개는 `entities/mod.rs` 또는 해당 모듈/함수에 `#[allow(clippy::disallowed_methods)]`로 한정. `migration/clippy.toml`, `entity/clippy.toml`에서는 unwrap 규칙 미적용.
Expand Down
38 changes: 24 additions & 14 deletions packages/api-server/docs/GIT_WORKFLOW.md
Original file line number Diff line number Diff line change
@@ -1,32 +1,42 @@
# Git 워크플로 (백엔드)
# Git 워크플로 (모노레포 · 백엔드 포함)

## `main` 직접 push 금지

`scripts/pre-push.sh`는 **Git `pre-push` 훅으로 연결**되면, 원격 ref가 `refs/heads/main`(또는 `master`)일 때 push를 거부합니다. 반드시 **토픽 브랜치 → PR → 머지**로 진행하세요.
모노레포 **루트**의 [scripts/git-pre-push.sh](../../../scripts/git-pre-push.sh)가 `pre-push` 훅으로 연결되면, 원격 ref가 `refs/heads/main`(또는 `master`)일 때 push를 거부합니다. **토픽 브랜치 → PR → 머지**로 진행하세요.

`backend` 저장소 루트(이 레포의 루트 = `Cargo.toml` 있는 곳)에서:
### 훅 설치 (권장: `core.hooksPath`)

저장소 **루트**(`decoded-monorepo/` — `package.json` 있는 곳)에서:

```bash
chmod +x scripts/pre-push.sh
ln -sf ../../scripts/pre-push.sh .git/hooks/pre-push
chmod +x scripts/git-pre-push.sh .githooks/pre-push packages/ai-server/scripts/pre-push.sh
git config core.hooksPath .githooks
```

이후 `git push` 시 자동으로 이 스크립트가 먼저 실행됩니다 (`main`/`master` 직접 push는 거부).
또는 루트 [Justfile](../../../Justfile): `just hook`

이후 `git push` 시 자동으로 루트 스크립트가 실행됩니다 (`main`/`master` 직접 push는 거부).

**just** 사용 시: 저장소 루트에서 `just hook` 또는 온보딩용으로 **`just dev`** / **`just setup`**(훅 + `.env.dev` 없으면 생성 + `docker/dev` 스택 기동) — [`justfile`](../justfile) 참고.
**just (api-server 패키지)** 를 쓰는 경우에도, 훅은 **모노레포 루트**의 `core.hooksPath=.githooks` 를 쓰는 것이 맞습니다. [packages/api-server/justfile](../justfile)의 `hook` 레시피가 루트를 가리킵니다.

원격 저장소(GitHub/GitLab 등)에서는 **브랜치 보호 규칙**으로 `main` 직접 push를 막는 것을 함께 권장합니다(훅은 우회 가능).
원격 저장소에서는 **브랜치 보호 규칙**으로 `main` 직접 push를 막는 것을 함께 권장합니다(훅은 `--no-verify`로 우회 가능).

자동 CI는 **로컬 `pre-push`만** 사용합니다(GitHub Actions 워크플로 없음).
자동 CI는 **로컬 `pre-push`** 를 사용합니다(GitHub Actions 워크플로 없음).

**`git push --no-verify`는 쓰지 마세요.** 훅을 건너뛰면 포맷·clippy·테스트·커버리지·마이그레이션 검사 없이 푸시됩니다. 코드 리뷰 전에 팀 합의된 절차로 한 번 더 돌리는 것을 권장합니다.
**`git push --no-verify`는 쓰지 마세요.** 훅을 건너뛰면 로컬 검사 없이 푸시됩니다.

## 로컬 CI
## 로컬 CI (전체 모노레포)

수동 실행(훅의 `main` 차단은 건너뜀 — TTY):
저장소 루트에서:

```bash
cd backend && bash scripts/pre-push.sh
bun run ci:local
# 또는
bash scripts/git-pre-push.sh
```

포함: `cargo fmt --check`, `clippy -D warnings`, `cargo test --lib`, **`cargo-deny`**, **`cargo-tarpaulin`**(라인 **10%** 미만 실패; `lib`, `src/entities/*` 제외), **`check-migration-sync.sh`**(`DATABASE_URL`·`psql` 필수; `backend/.env` 또는 `.env.dev`). (`cargo-deny` / `cargo-tarpaulin` 미설치 시 push 불가)
포함(순서): **프론트 슬롯**(현재 플레이스홀더) → **ai-server**(선택 — 아래) → **api-server**([packages/api-server/scripts/pre-push.sh](../scripts/pre-push.sh): `cargo fmt`, clippy, `cargo test --lib`, `cargo-deny`, `cargo-tarpaulin`, `check-migration-sync.sh` — `DATABASE_URL`·`psql` 등 기존과 동일).

- **ai-server**([packages/ai-server/scripts/pre-push.sh](../../ai-server/scripts/pre-push.sh): flake8, `black --check`, pytest CI 마커 서브집합): 기본 **건너뜀**(레거시 flake8 이슈 정리 후 팀에서 켜기). 실행하려면 `RUN_AI_SERVER_CI=1 bun run ci:local`. 단독: `cd packages/ai-server && bun run ci:local`. `uv`·`uv sync --group dev` 필요.
- ai-server 강제 스킵: `SKIP_AI_SERVER_CI=1`( `RUN_AI_SERVER_CI` 와 함께 쓸 일 거의 없음)
- api-server만 (기존): `bash packages/api-server/scripts/pre-push.sh`
2 changes: 1 addition & 1 deletion packages/api-server/docs/TESTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export DATABASE_URL="postgresql://..."

## 로컬 품질 게이트

- [`scripts/pre-push.sh`](../scripts/pre-push.sh): `fmt`, `clippy -D warnings`, **`cargo test --lib`** (단위만), **`cargo-deny`**, **`cargo-tarpaulin`**(라인 커버리지 **10%** 미만 시 실패; `lib`만, `src/entities/*` 제외 — 각각 `cargo install cargo-deny`, `cargo install cargo-tarpaulin` 필요), **`check-migration-sync.sh`**(`DATABASE_URL`·`psql` 필수; `backend/.env` 또는 `.env.dev`에서 로드 가능). `decoded-api` / `migration` / `entity`의 `Cargo.toml`에 `[lints.rust] unused_imports = "deny"`가 있어 **미사용 import는 Clippy 단계에서 실패**합니다.
- [`scripts/pre-push.sh`](../scripts/pre-push.sh): `fmt`, `clippy -D warnings`, **`cargo test --lib`** (단위만), **`cargo-deny`**, **`cargo-tarpaulin`**(라인 커버리지 **10%** 미만 시 실패; `lib`만, `src/entities/*` 제외 — 각각 `cargo install cargo-deny`, `cargo install cargo-tarpaulin` 필요), **`check-migration-sync.sh`**(`DATABASE_URL`·`psql` 필수; `packages/api-server/.env` 또는 `.env.dev`에서 로드 가능). 모노레포 전체 흐름은 루트 `scripts/git-pre-push.sh` / `bun run ci:local` — [GIT_WORKFLOW.md](GIT_WORKFLOW.md). `decoded-api` / `migration` / `entity`의 `Cargo.toml`에 `[lints.rust] unused_imports = "deny"`가 있어 **미사용 import는 Clippy 단계에서 실패**합니다.

### 커버리지 측정 (로컬)

Expand Down
13 changes: 8 additions & 5 deletions packages/api-server/justfile
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
# 설치: brew install just (또는 cargo install just)
# 명령 목록: just --list
#
# 이 justfile 이 있는 디렉터리 = backend 저장소 루트(Cargo.toml)에서 실행하세요.
# 이 justfile 은 packages/api-server 에 있습니다. 모노레포 루트의 Git 훅을 설정합니다.

compose_dev := "docker/dev/docker-compose.yml"

Expand All @@ -16,11 +16,14 @@ dev: hook env-dev docker-up
# `dev` 와 동일 (온보딩용 이름)
setup: dev

# Git pre-push 훅 (`git push` 전 로컬 CI + main/master 직접 push 차단)
# Git pre-push — 모노레포 루트 `core.hooksPath=.githooks` (루트 Justfile `hook` 과 동일)
hook:
chmod +x scripts/pre-push.sh
ln -sf ../../scripts/pre-push.sh .git/hooks/pre-push
@echo "OK: .git/hooks/pre-push -> scripts/pre-push.sh"
#!/usr/bin/env bash
set -euo pipefail
root="$(cd "{{ justfile_directory() }}/../.." && pwd)"
chmod +x "$root/scripts/git-pre-push.sh" "$root/.githooks/pre-push" "$root/packages/ai-server/scripts/pre-push.sh"
git -C "$root" config core.hooksPath .githooks
echo "OK: git -C $root config core.hooksPath=.githooks"

# `.env.dev` 없으면 `.env.example`에서 복사 (docker compose 가 참조)
env-dev:
Expand Down
7 changes: 4 additions & 3 deletions packages/api-server/scripts/pre-push.sh
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
#!/usr/bin/env bash
# 로컬 CI — git push 전에 실행 권장:
# (api-server 디렉터리에서) ln -sf ../../scripts/pre-push.sh .git/hooks/pre-push
# api-server 로컬 CI — 단독 실행 가능. 모노레포에서는 루트 `scripts/git-pre-push.sh`가 호출합니다.
# 훅 설치: 모노레포 루트에서 `git config core.hooksPath .githooks` (또는 `just hook`)
set -euo pipefail

# Git이 이 스크립트를 pre-push 훅으로 호출할 때만 stdin으로 ref 정보가 옴.
# Git이 이 스크립트를 pre-push 훅으로 직접 연결한 경우에만 stdin으로 ref 정보가 옴.
# 루트 git-pre-push.sh 가 stdin을 이미 읽었으면 여기서는 while 가 돌지 않음.
# 수동 실행(`./pre-push.sh`)은 TTY라서 이 검사를 건너뜀.
if ! [ -t 0 ]; then
while read -r local_ref local_sha remote_ref remote_sha; do
Expand Down
50 changes: 50 additions & 0 deletions scripts/git-pre-push.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
#!/usr/bin/env bash
# 모노레포 루트 로컬 CI — `git push` 전 실행.
# 설치: 저장소 루트에서 `git config core.hooksPath .githooks` (또는 `just hook`)
# 수동: `bash scripts/git-pre-push.sh` / `bun run ci:local`
set -euo pipefail

_self="${BASH_SOURCE[0]:-$0}"
while [ -h "$_self" ]; do
_dir="$(cd -P "$(dirname "$_self")" && pwd)"
_link="$(readlink "$_self" || true)"
case "$_link" in
/*) _self="$_link" ;;
*) _self="$_dir/$_link" ;;
esac
done
REPO_ROOT="$(cd -P "$(dirname "$_self")/.." && pwd)"
cd "$REPO_ROOT"

# Git 훅 호출 시 stdin으로 ref 목록이 옴. 여기서 한 번 소비해 main/master 직접 push 차단.
# (api-server pre-push.sh에도 동일 블록이 있으나, stdin은 한 번만 읽을 수 있음)
if ! [ -t 0 ]; then
while read -r local_ref local_sha remote_ref remote_sha; do
[ -z "${remote_ref:-}" ] && continue
case "$remote_ref" in
refs/heads/main | refs/heads/master)
printf '%s\n' "error: remote '${remote_ref#refs/heads/}'(으)로 직접 push 할 수 없습니다. 브랜치를 만들고 PR로 머지하세요." >&2
exit 1
;;
esac
done
fi

echo "=== [monorepo] 프론트 CI 슬롯 (미구현) ==="
# TODO(@frontend): eslint / turbo / tsc 등 — 예: bun run --filter @decoded/web lint
true

# ai-server 는 flake8 정리 등 팀 준비 전까지 기본 건너뜀. 켜기: RUN_AI_SERVER_CI=1
if [[ -n "${SKIP_AI_SERVER_CI:-}" ]]; then
echo "=== [monorepo] ai-server CI 건너뜀 (SKIP_AI_SERVER_CI) ==="
elif [[ -n "${RUN_AI_SERVER_CI:-}" ]]; then
echo "=== [monorepo] ai-server 로컬 CI ==="
bash "$REPO_ROOT/packages/ai-server/scripts/pre-push.sh"
else
echo "=== [monorepo] ai-server CI 건너뜀 (기본 — 켜려면 RUN_AI_SERVER_CI=1) ==="
fi

echo "=== [monorepo] api-server 로컬 CI ==="
bash "$REPO_ROOT/packages/api-server/scripts/pre-push.sh"

echo "=== [monorepo] 모든 체크 통과 ==="