From b3d21f17d298c1c04ea6abf807f29190b6b6b692 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 14 Jun 2026 22:27:26 -0700 Subject: [PATCH] =?UTF-8?q?chore:=20Makefile=E2=86=92justfile=20+=20?= =?UTF-8?q?=EA=B0=9C=EB=B0=9C=20=ED=8F=AC=ED=8A=B8=20unique=ED=99=94(31300?= =?UTF-8?q?/31800)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Makefile 삭제 → justfile. `just run dev`(둘 다) / `just run fe` / `just run be` + install/test/e2e/lint/format/health/seed/migrate/clean 레시피 - 개발 포트를 흔한 dev 포트(3000/5173/8000/8080)와 충돌 없는 31xxx 대역으로: 프론트 :3000→:31300, 백엔드 :8000→:31800. 전 참조 일괄 변경 (package.json/next.config/lib api 기본값/playwright config·fixture/.env*.example/ config.py FRONTEND_ORIGIN/scripts/dev.sh/CI/README) - E2E 견고화: inbox/tasks/dashboard spec 에 beforeAll(resetSeed) 추가 → 직렬 전체 실행에서 앞 spec 의 캡처 누적과 격리(결정성↑, 전체 39 e2e 29.7s) 검증(새 포트): 백엔드 pytest 63 · 프론트 vitest 69 · playwright 39 전부 통과, tsc/eslint/ruff clean, build OK. 라이브: :31300↔:31800 CORS·SSR fetch·/ 리다이렉트 정상, `just health`/`just run` 동작, 옛 포트 참조 0. Co-Authored-By: Claude Opus 4.8 (1M context) --- .github/workflows/ci.yml | 2 +- Makefile | 59 -------------------- README.md | 29 +++++----- backend/.env.example | 2 +- backend/app/config.py | 3 +- frontend/lib/api.ts | 4 +- frontend/lib/dashboard/api.ts | 2 +- frontend/lib/inbox/api.ts | 2 +- frontend/next.config.ts | 4 +- frontend/package.json | 4 +- frontend/playwright.config.ts | 4 +- frontend/playwright/dashboard.spec.ts | 4 ++ frontend/playwright/fixtures/seed-reset.ts | 2 +- frontend/playwright/inbox.spec.ts | 4 ++ frontend/playwright/tasks.spec.ts | 6 +- justfile | 64 ++++++++++++++++++++++ scripts/dev.sh | 4 +- 17 files changed, 110 insertions(+), 89 deletions(-) delete mode 100644 Makefile create mode 100644 justfile diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d3034dd..4293762 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -40,7 +40,7 @@ jobs: - uses: astral-sh/setup-uv@v3 - run: uv sync && uv run alembic upgrade head && uv run python -m app.seed working-directory: backend - - run: ARI_ALLOW_TEST_RESET=1 LLM_PROVIDER=heuristic uv run uvicorn app.main:app --port 8000 & + - run: ARI_ALLOW_TEST_RESET=1 LLM_PROVIDER=heuristic uv run uvicorn app.main:app --port 31800 & working-directory: backend - uses: pnpm/action-setup@v4 - uses: actions/setup-node@v4 diff --git a/Makefile b/Makefile deleted file mode 100644 index 118a61a..0000000 --- a/Makefile +++ /dev/null @@ -1,59 +0,0 @@ -# Makefile (레포 루트) -.PHONY: help install fe be dev test lint format health seed migrate clean - -FRONT := frontend -BACK := backend - -help: - @echo "make install - 프론트/백 의존성 설치" - @echo "make dev - 프론트(3000)+백(8000) 동시 실행" - @echo "make fe - 프론트만 (next dev)" - @echo "make be - 백엔드만 (uvicorn --reload)" - @echo "make test - pytest + vitest" - @echo "make lint - ruff + eslint" - @echo "make health - 두 health 엔드포인트 curl" - @echo "make seed - DB 시드 적재" - @echo "make migrate - alembic upgrade head" - -install: - cd $(FRONT) && pnpm install - cd $(BACK) && uv sync - -fe: - cd $(FRONT) && pnpm dev - -be: - cd $(BACK) && uv run uvicorn app.main:app --reload --port 8000 - -# 두 서버 동시 실행: Ctrl-C 시 둘 다 정리. -dev: - @echo "▶ backend :8000 / frontend :3000 (Ctrl-C 로 종료)" - @trap 'kill 0' INT TERM EXIT; \ - ( cd $(BACK) && uv run uvicorn app.main:app --reload --port 8000 ) & \ - ( cd $(FRONT) && pnpm dev ) & \ - wait - -test: - cd $(BACK) && uv run pytest - cd $(FRONT) && pnpm test - -lint: - cd $(BACK) && uv run ruff check . - cd $(FRONT) && pnpm lint - -format: - cd $(BACK) && uv run ruff format . && uv run black . - cd $(FRONT) && pnpm format - -health: - @echo "── /api/health ──" && curl -s http://localhost:8000/api/health | python3 -m json.tool - @echo "── /api/llm/health ──" && curl -s http://localhost:8000/api/llm/health | python3 -m json.tool - -seed: - cd $(BACK) && uv run python -m app.seed - -migrate: - cd $(BACK) && uv run alembic upgrade head - -clean: - rm -rf $(FRONT)/.next $(BACK)/ari.db $(BACK)/.pytest_cache diff --git a/README.md b/README.md index 2ed87b3..63e65a7 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,9 @@ workspace/ └─ design-reference/ 원본 프로토타입 (픽셀 충실 재현 기준) ``` +> **개발 포트**: 흔한 dev 포트(3000/5173/8000/8080)와 충돌을 피하려고 **프론트 :31300 · 백엔드 :31800** 을 씁니다. +> 작업 러너는 `make` 대신 **`just`** 입니다(`justfile`). + ## 스택 | 레이어 | 선택 | @@ -27,17 +30,17 @@ workspace/ ## 빠른 시작 ### 0) 사전 설치 -Node ≥ 20 · pnpm · Python ≥ 3.11 · uv · (선택) Ollama +Node ≥ 20 · pnpm · Python ≥ 3.11 · uv · **just** · (선택) Ollama ### 1) 의존성 + 환경변수 + 시드 ```bash -make install # frontend(pnpm) + backend(uv) 의존성 +just install # frontend(pnpm) + backend(uv) 의존성 cp backend/.env.example backend/.env # 필요 시 OLLAMA_MODEL 등 수정 cp frontend/.env.local.example frontend/.env.local -cd backend && uv run alembic upgrade head && uv run python -m app.seed && cd .. +just migrate && just seed # alembic upgrade head + 시드 적재 ``` ### 2) (선택) Ollama — 모델 비종속 @@ -53,20 +56,20 @@ ollama pull <설치할_모델> # backend/.env 의 OLLAMA_MODEL 과 일치 ### 3) 실행 ```bash -make dev # backend :8000 + frontend :3000 (또는 scripts/dev.sh) -# http://localhost:3000 → / 는 /dashboard 로 리다이렉트 +just run dev # backend :31800 + frontend :31300 (개별: just run be / just run fe) +# http://localhost:31300 → / 는 /dashboard 로 리다이렉트 ``` ### 4) 스모크 / 테스트 ```bash -make health # /api/health, /api/llm/health -make test # pytest + vitest -make lint # ruff + eslint +just health # /api/health, /api/llm/health +just test # pytest + vitest +just lint # ruff + eslint # E2E (백엔드는 리셋 허용 + heuristic 으로 기동해야 federation/a11y/responsive 전부 통과) -ARI_ALLOW_TEST_RESET=1 LLM_PROVIDER=heuristic uv run --directory backend uvicorn app.main:app --port 8000 & -cd frontend && pnpm exec playwright test +ARI_ALLOW_TEST_RESET=1 LLM_PROVIDER=heuristic uv run --directory backend uvicorn app.main:app --port 31800 & +just e2e # = pnpm exec playwright test --workers=1 ``` ## 시드 초기화(데모 리셋) @@ -74,7 +77,7 @@ cd frontend && pnpm exec playwright test ```bash cd backend && rm -f ari.db && uv run alembic upgrade head && uv run python -m app.seed # 또는 E2E용(가드): ARI_ALLOW_TEST_RESET=1 로 기동 후 -curl -X POST http://localhost:8000/api/_test/reset +curl -X POST http://localhost:31800/api/_test/reset ``` ## 환경변수 @@ -86,9 +89,9 @@ curl -X POST http://localhost:8000/api/_test/reset | `LLM_PROVIDER` | backend env | `auto` | `auto`\|`ollama`\|`heuristic` (테스트/E2E는 `heuristic`) | | `LLM_TIMEOUT` | backend env | `30` | 분류 1건 타임아웃(초). 초과 시 heuristic 폴백 | | `DATABASE_URL` | backend `.env` | `sqlite:///./ari.db` | DB 경로 | -| `FRONTEND_ORIGIN` | backend `.env` | `http://localhost:3000` | CORS 허용 출처(콤마구분) | +| `FRONTEND_ORIGIN` | backend `.env` | `http://localhost:31300` | CORS 허용 출처(콤마구분) | | `ARI_ALLOW_TEST_RESET` | backend env | (미설정) | `1`이면 `/api/_test/reset` 허용(E2E 전용, 운영 403) | -| `NEXT_PUBLIC_API_BASE` | frontend `.env.local` | `http://localhost:8000` | 프론트가 호출할 백엔드 베이스 | +| `NEXT_PUBLIC_API_BASE` | frontend `.env.local` | `http://localhost:31800` | 프론트가 호출할 백엔드 베이스 | ## 연합(federation) 흐름 — MVP의 핵심 diff --git a/backend/.env.example b/backend/.env.example index d28bed6..cc46d7c 100644 --- a/backend/.env.example +++ b/backend/.env.example @@ -1,4 +1,4 @@ DATABASE_URL=sqlite:///./ari.db -FRONTEND_ORIGIN=http://localhost:3000 +FRONTEND_ORIGIN=http://localhost:31300 OLLAMA_HOST=http://localhost:11434 OLLAMA_MODEL=llama3.1 diff --git a/backend/app/config.py b/backend/app/config.py index 633f3b7..266cef9 100644 --- a/backend/app/config.py +++ b/backend/app/config.py @@ -19,7 +19,8 @@ class Settings(BaseSettings): risk_today: int = 8 # CORS 허용 출처(콤마구분). 단일 환경변수 규약: FRONTEND_ORIGIN. - frontend_origin: str = "http://localhost:3000" + # 개발 포트는 흔한 dev 포트와 충돌을 피해 31xxx 대역 사용(프론트 31300). + frontend_origin: str = "http://localhost:31300" # 테스트 리셋 게이트 (테스트 환경에서만 시드 리셋 허용) ari_allow_test_reset: bool = False diff --git a/frontend/lib/api.ts b/frontend/lib/api.ts index 70a9f86..06b2230 100644 --- a/frontend/lib/api.ts +++ b/frontend/lib/api.ts @@ -1,7 +1,7 @@ // frontend/lib/api.ts -export const API_BASE = process.env.NEXT_PUBLIC_API_BASE ?? "http://localhost:8000"; +export const API_BASE = process.env.NEXT_PUBLIC_API_BASE ?? "http://localhost:31800"; -/** /api 프리픽스 경로를 절대 URL로. 예: apiUrl("/health") → http://localhost:8000/api/health */ +/** /api 프리픽스 경로를 절대 URL로. 예: apiUrl("/health") → http://localhost:31800/api/health */ export function apiUrl(path: string): string { const clean = path.startsWith("/") ? path : `/${path}`; return `${API_BASE}/api${clean}`; diff --git a/frontend/lib/dashboard/api.ts b/frontend/lib/dashboard/api.ts index 823e964..4a49327 100644 --- a/frontend/lib/dashboard/api.ts +++ b/frontend/lib/dashboard/api.ts @@ -1,7 +1,7 @@ // frontend/lib/dashboard/api.ts import type { Dashboard } from "@/lib/types"; -const BASE = process.env.NEXT_PUBLIC_API_BASE ?? "http://localhost:8000"; +const BASE = process.env.NEXT_PUBLIC_API_BASE ?? "http://localhost:31800"; export async function getDashboard(signal?: AbortSignal): Promise { const res = await fetch(`${BASE}/api/dashboard`, { signal, cache: "no-store" }); diff --git a/frontend/lib/inbox/api.ts b/frontend/lib/inbox/api.ts index bca88fa..5165b56 100644 --- a/frontend/lib/inbox/api.ts +++ b/frontend/lib/inbox/api.ts @@ -1,7 +1,7 @@ // frontend/lib/inbox/api.ts import type { CaptureResult, ConfirmResult, InboxItem, RouteType } from "@/lib/types"; -const BASE = process.env.NEXT_PUBLIC_API_BASE ?? "http://localhost:8000"; +const BASE = process.env.NEXT_PUBLIC_API_BASE ?? "http://localhost:31800"; export class ApiError extends Error { constructor( diff --git a/frontend/next.config.ts b/frontend/next.config.ts index 55064bf..0c9f0cf 100644 --- a/frontend/next.config.ts +++ b/frontend/next.config.ts @@ -1,9 +1,9 @@ import type { NextConfig } from "next"; -const API_BASE = process.env.NEXT_PUBLIC_API_BASE ?? "http://localhost:8000"; +const API_BASE = process.env.NEXT_PUBLIC_API_BASE ?? "http://localhost:31800"; const nextConfig: NextConfig = { - // dev 프록시: 프론트의 상대 경로 /api/* 호출을 백엔드(:8000)로 전달. + // dev 프록시: 프론트의 상대 경로 /api/* 호출을 백엔드(:31800)로 전달. // lib/api.ts 는 절대 URL(API_BASE)을 쓰지만, 상대 경로 사용 시에도 동작하도록 둔다. async rewrites() { return [ diff --git a/frontend/package.json b/frontend/package.json index 8938c05..5bcb539 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -3,9 +3,9 @@ "version": "0.1.0", "private": true, "scripts": { - "dev": "next dev -p 3000", + "dev": "next dev -p 31300", "build": "next build", - "start": "next start -p 3000", + "start": "next start -p 31300", "lint": "eslint", "format": "prettier --write .", "format:check": "prettier --check .", diff --git a/frontend/playwright.config.ts b/frontend/playwright.config.ts index f682c38..68956cf 100644 --- a/frontend/playwright.config.ts +++ b/frontend/playwright.config.ts @@ -10,7 +10,7 @@ export default defineConfig({ retries: 1, reporter: [["list"]], use: { - baseURL: "http://localhost:3000", + baseURL: "http://localhost:31300", trace: "on-first-retry", navigationTimeout: 45_000, actionTimeout: 15_000, @@ -18,7 +18,7 @@ export default defineConfig({ projects: [{ name: "chromium", use: { ...devices["Desktop Chrome"] } }], webServer: { command: "pnpm dev", - url: "http://localhost:3000", + url: "http://localhost:31300", reuseExistingServer: !process.env.CI, timeout: 120_000, }, diff --git a/frontend/playwright/dashboard.spec.ts b/frontend/playwright/dashboard.spec.ts index c04b928..83ee3b2 100644 --- a/frontend/playwright/dashboard.spec.ts +++ b/frontend/playwright/dashboard.spec.ts @@ -1,8 +1,12 @@ // frontend/playwright/dashboard.spec.ts import { expect, test } from "@playwright/test"; +import { resetSeed } from "./fixtures/seed-reset"; test.describe.configure({ mode: "serial" }); +// 깨끗한 시드에서 시작(앞선 spec 의 변경과 격리). +test.beforeAll(resetSeed); + test("로드 → 집계가 시드 기준으로 표시", async ({ page }) => { await page.goto("/dashboard"); await expect(page.getByText("아리 브리핑")).toBeVisible(); diff --git a/frontend/playwright/fixtures/seed-reset.ts b/frontend/playwright/fixtures/seed-reset.ts index 2c09907..7fc8d16 100644 --- a/frontend/playwright/fixtures/seed-reset.ts +++ b/frontend/playwright/fixtures/seed-reset.ts @@ -1,7 +1,7 @@ // frontend/playwright/fixtures/seed-reset.ts import { request } from "@playwright/test"; -const API = process.env.API_BASE ?? "http://localhost:8000"; +const API = process.env.API_BASE ?? "http://localhost:31800"; /** 백엔드 시드를 초기 상태로 되돌린다. ARI_ALLOW_TEST_RESET=1 일 때만 동작. */ export async function resetSeed(): Promise { diff --git a/frontend/playwright/inbox.spec.ts b/frontend/playwright/inbox.spec.ts index 634f4f4..533e008 100644 --- a/frontend/playwright/inbox.spec.ts +++ b/frontend/playwright/inbox.spec.ts @@ -3,9 +3,13 @@ // 캡처는 인박스 시드를 변경하므로 단일 워커 직렬. import AxeBuilder from "@axe-core/playwright"; import { expect, test } from "@playwright/test"; +import { resetSeed } from "./fixtures/seed-reset"; test.describe.configure({ mode: "serial" }); +// 전체 스위트(직렬)에서 앞선 spec 의 캡처 누적과 무관하게 깨끗한 시드에서 시작. +test.beforeAll(resetSeed); + test("캡처 → 분류 → 좋아요 → 작업 페이지에 등장(연합)", async ({ page }) => { await page.goto("/inbox"); const input = page.getByLabel("인박스에 빠르게 캡처"); diff --git a/frontend/playwright/tasks.spec.ts b/frontend/playwright/tasks.spec.ts index 6ae16aa..d58185d 100644 --- a/frontend/playwright/tasks.spec.ts +++ b/frontend/playwright/tasks.spec.ts @@ -1,9 +1,13 @@ // frontend/playwright/tasks.spec.ts -// 주의: 백엔드(:8000) 시드 DB 를 변경하는 테스트 포함 → 단일 워커 직렬 실행. +// 주의: 백엔드(:31800) 시드 DB 를 변경하는 테스트 포함 → 단일 워커 직렬 실행. import { expect, test } from "@playwright/test"; +import { resetSeed } from "./fixtures/seed-reset"; test.describe.configure({ mode: "serial" }); +// 깨끗한 시드에서 시작(앞선 spec 의 변경과 격리). +test.beforeAll(resetSeed); + test("초기 진입: 업무 스코프 + 칸반 5컬럼 + 리스크 레이더", async ({ page }) => { await page.goto("/tasks"); await expect(page.locator(".tree-row.folder.on .tree-name")).toHaveText("업무"); diff --git a/justfile b/justfile new file mode 100644 index 0000000..4920587 --- /dev/null +++ b/justfile @@ -0,0 +1,64 @@ +# 아리 — 작업 러너 (make 대체). `just --list` 로 전체 보기. +# 개발 포트는 흔한 dev 포트(3000/5173/8000/8080)와 충돌을 피해 31xxx 대역 사용. + +be_port := "31800" # 백엔드 API +fe_port := "31300" # 프론트(package.json `dev` 스크립트도 -p {{fe_port}}) + +# 기본: 사용 가능한 레시피 목록 +default: + @just --list + +# 의존성 설치 (프론트 pnpm + 백엔드 uv) +install: + cd frontend && pnpm install + cd backend && uv sync + +# 실행: `just run dev`(둘 다) | `just run fe` | `just run be` +run target="dev": + #!/usr/bin/env bash + set -euo pipefail + case "{{target}}" in + dev) + echo "▶ backend :{{be_port}} / frontend :{{fe_port}} (Ctrl-C 로 종료)" + trap 'kill 0' INT TERM EXIT + ( cd backend && uv run uvicorn app.main:app --reload --port {{be_port}} ) & + ( cd frontend && pnpm dev ) & + wait ;; + fe) cd frontend && pnpm dev ;; + be) cd backend && uv run uvicorn app.main:app --reload --port {{be_port}} ;; + *) echo "알 수 없는 대상: {{target}} (dev|fe|be)"; exit 1 ;; + esac + +# 테스트 (백엔드 pytest + 프론트 vitest) +test: + cd backend && uv run pytest + cd frontend && pnpm test + +# E2E (Playwright). 백엔드는 ARI_ALLOW_TEST_RESET=1 LLM_PROVIDER=heuristic 로 기동해야 전부 통과. +e2e: + cd frontend && pnpm exec playwright test --workers=1 + +# 린트 (ruff + eslint) / 포맷 +lint: + cd backend && uv run ruff check . + cd frontend && pnpm lint + +format: + cd backend && uv run ruff format . && uv run black . + cd frontend && pnpm format + +# health 엔드포인트 확인 +health: + @echo "── /api/health ──" && curl -s http://localhost:{{be_port}}/api/health | python3 -m json.tool + @echo "── /api/llm/health ──" && curl -s http://localhost:{{be_port}}/api/llm/health | python3 -m json.tool + +# DB 시드 / 마이그레이션 +seed: + cd backend && uv run python -m app.seed + +migrate: + cd backend && uv run alembic upgrade head + +# 빌드 산출물 정리 +clean: + rm -rf frontend/.next backend/ari.db backend/.pytest_cache diff --git a/scripts/dev.sh b/scripts/dev.sh index 786f1a3..c6c23a5 100755 --- a/scripts/dev.sh +++ b/scripts/dev.sh @@ -1,9 +1,9 @@ #!/usr/bin/env bash -# 아리 — backend(:8000) + frontend(:3000) 동시 기동. Ctrl-C 로 둘 다 정리. +# 아리 — backend(:31800) + frontend(:31300) 동시 기동. Ctrl-C 로 둘 다 정리. set -euo pipefail ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" trap 'kill 0' INT TERM EXIT -( cd "$ROOT/backend" && uv run uvicorn app.main:app --reload --port 8000 ) & +( cd "$ROOT/backend" && uv run uvicorn app.main:app --reload --port 31800 ) & ( cd "$ROOT/frontend" && pnpm dev ) & wait