# 기여

> GitHub에서 Nodaro에 코드를 기여하세요. 개발 환경을 준비하고 dev에서 브랜치를 만든 뒤, 테스트를 실행하고 changeset을 추가하고 기여자 계약에 서명합니다.

Source: https://nodaro.ai/ko/docs/self-hosting/contributing

Nodaro에 **기여**한다는 것은 공개 리포지토리 [github.com/nodaroai/app.nodaro.ai](https://github.com/nodaroai/app.nodaro.ai)에 풀 리퀘스트로 코드를 보내는 것입니다. Nodaro는 Nodaro Sustainable Use License에 따라 source-available로 제공됩니다. 이 페이지에서는 개발 환경 설정, 브랜치, 풀 리퀘스트, 테스트, 기여자 계약을 다룹니다.

내 서버에서 Nodaro를 실행하려는 경우라면 [빠른 시작](https://nodaro.ai/docs/self-hosting/quickstart)부터 보세요. API를 기반으로 개발하려면 [SDK](https://nodaro.ai/docs/developers/sdk)를 참고하세요.

## 리포지토리
리포지토리는 npm 워크스페이스로 구성된 모노레포 하나입니다.

| 폴더 | 들어 있는 것 |
| --- | --- |
| `backend/` | Fastify API, BullMQ 워커, 오케스트레이터. Node.js 22, TypeScript. |
| `frontend/` | Vite 앱: 편집기, 앱 실행기, 관리자 패널. React 19, React Router 7, React Flow. |
| `packages/` | 스택 전체에서 공유하는 순수 로직인 `@nodaro/shared`, 타입이 지정된 REST 클라이언트인 `@nodaro/sdk`, `@nodaro/cli`, 프롬프트 계층인 `@nodaro/prompts`, Remotion 동영상 컴포지션. |
| `supabase/` | 앞으로만 진행되는 SQL 마이그레이션으로 관리하는 데이터베이스 스키마. |
| `docs/` | 리포지토리 안의 문서. 주요 기능의 설계 이유를 설명하는 설계 노트도 포함됩니다. |
| `scripts/` | 아키텍처 그래프 생성기와 감사 도구 같은 리포지토리 유틸리티. |
| `.changeset/` | 공개 패키지의 대기 중인 버전 올림. |

프로젝트 규칙은 리포지토리 루트의 `CLAUDE.md`에 있습니다. 코딩 표준, 모델 공급자 추가 체크리스트, 노드 추가 체크리스트가 담겨 있습니다. 간단하지 않은 풀 리퀘스트를 보내기 전에 읽어 보세요. 구성 요소가 어떻게 맞물리는지는 [아키텍처](https://nodaro.ai/docs/self-hosting/architecture)에서 설명합니다.

## 개발 환경 준비
npm이 포함된 Node.js 22 이상이 필요합니다.

### 클론과 설치
```bash
git clone https://github.com/nodaroai/app.nodaro.ai
cd app.nodaro.ai
# linux/x64 only: skip the CUDA download of onnxruntime-node
export ONNXRUNTIME_NODE_INSTALL=skip
npm install        # installs every workspace
```

linux/x64에서는 설치하기 전에 `export` 줄을 실행하세요. 이 줄이 없으면 얼굴 감지기의 런타임인 `onnxruntime-node`를 설치할 때 CUDA 12와 TensorRT 라이브러리를 내려받습니다. 얼굴 감지기는 이 라이브러리를 불러오지 않으며, 나중에 변수를 설정하고 다시 설치해도 이미 받은 라이브러리는 지워지지 않습니다. 이후 설치에서도 매번 다운로드를 건너뛰도록 이 줄을 셸 프로필에 넣으세요. macOS, Windows, linux/arm64에서는 이 라이브러리를 내려받지 않습니다.

### 환경 구성
```bash
cp .env.example .env
```

최소한 `SUPABASE_URL`, `SUPABASE_SERVICE_ROLE_KEY`, `SUPABASE_ANON_KEY`, `INTERNAL_ORCHESTRATOR_SECRET`과 `KIE_API_KEY`, `REPLICATE_API_TOKEN`, `ANTHROPIC_API_KEY` 같은 공급자 키 하나를 설정하세요. 시크릿을 생성하세요.

```bash
echo "INTERNAL_ORCHESTRATOR_SECRET=$(openssl rand -hex 32)" >> .env
echo "NODARO_ENCRYPTION_KEY=$(openssl rand -hex 32)" >> .env
```

가장 쉬운 데이터베이스는 supabase.com의 무료 프로젝트입니다. Supabase CLI의 `supabase start`로 완전한 로컬 스택을 띄워도 되지만 설정이 더 필요합니다. 워크플로 실행에는 Redis도 필요하며, 기본값이 `redis://localhost:6379`인 `REDIS_URL`로 지정합니다. `.env.example`에 지원하는 모든 변수가 나열되어 있고, [구성](https://nodaro.ai/docs/self-hosting/configuration)에서 각 변수를 설명합니다.

### 공유 패키지를 한 번 빌드
프런트엔드는 `@nodaro/shared`를 빌드 출력에서 읽으므로, 처음 시작하기 전에 빌드하세요. 그 뒤에는 공유 코드를 편집하면 패키지 자체의 dev 스크립트가 다시 빌드합니다.

```bash
npm -w @nodaro/shared run build
```

### 개발 서버 시작
터미널 두 개에서 실행하세요.

```bash
# Terminal 1: the API on port 9000
cd backend
npm run dev

# Terminal 2: the frontend on port 3000, which forwards /v1/* to port 9000
cd frontend
npm run dev
```

## 코딩 표준
`CLAUDE.md`의 가장 중요한 규칙은 다음과 같습니다.

- **파일 크기.** 일반적으로 200~400줄이며, 800줄이 절대 상한입니다. 그보다 커지는 파일은 나누세요.
- **프로덕션 코드에 `console.log` 금지.** 기존 로거 패턴을 쓰세요.
- **Conventional Commits:** `feat:`, `fix:`, `refactor:`, `docs:`, `chore:`, `test:`와 구체적인 제목 줄을 씁니다.
- **커밋하기 전마다 타입 검사.** `backend/`와 `frontend/` 양쪽에서 `npx tsc --noEmit`을 실행하세요. 타입 검사를 통과하지 못한 풀 리퀘스트는 CI가 막습니다.
- **Express 라우터가 아니라 Fastify 플러그인.** 모든 라우트 파일은 Fastify 인스턴스를 받는 async 함수를 내보냅니다.
- **모든 API 엔드포인트에 Zod 스키마**를 예외 없이 둡니다. 스키마는 요청을 검증하고 OpenAPI 문서도 만들어 냅니다.
- **프런트엔드 상태:** 서버 상태는 React Query, 인터페이스 상태는 Zustand, 캔버스 상태는 React Flow로 관리합니다. 섞지 마세요.
- **객체나 배열을 절대 변경하지 마세요.** 항상 새 사본을 만드세요. Zustand와 React Flow 모두 참조 동등성에 의존합니다.

## 브랜치와 풀 리퀘스트
- 리포지토리에는 오래 유지되는 브랜치가 두 개 있습니다. 스테이징용 `dev`와 프로덕션용 `main`입니다.
- 리포지토리를 포크하고, **`dev`에서 브랜치를 만든 뒤** `dev`로 풀 리퀘스트를 여세요. `main`에서 브랜치를 만들거나 `main`에 직접 커밋하지 마세요.
- 브랜치 이름은 유형으로 시작하세요: `feat/`, `fix/`, `refactor/`, `docs/`, `chore/`, `test/`. 예: `feat/whisper-tts-node`.
- 병합된 변경 사항은 먼저 스테이징 인스턴스 `next.nodaro.ai`에서 실행됩니다. 약 24시간 검증한 뒤 메인테이너가 `dev`를 `main`으로 승격합니다.

풀 리퀘스트에서는 다음을 지켜 주세요.

- 관련된 GitHub 이슈를 연결하세요.
- 인터페이스를 바꾸는 경우 스크린숏이나 GIF를 추가하세요.
- 노드를 추가하거나 바꿀 때는 `CLAUDE.md`의 노드 체크리스트를 따르세요.
- 공개 패키지를 바꿀 때는 changeset을 추가하세요. [Changesets](#changesets)를 참고하세요.
- 데이터베이스 마이그레이션을 추가할 때는 다음 빈 번호로 `supabase/migrations/NNN_description.sql`이라고 이름을 붙이세요. 빈 번호는 `dev`와 열려 있는 모든 풀 리퀘스트에 있는 가장 큰 번호보다 1 큰 번호입니다. `supabase/migrations/.sequence`도 같은 번호로 설정하세요. **Migration Number Guard** 검사는 `dev`가 이미 그 번호를 쓰고 있으면 실패하며, 대신 쓸 번호를 메시지로 알려 줍니다.

## 테스트
워크스페이스마다 자체 Vitest 스위트가 있습니다. 리포지토리 루트에서 전부 실행하거나 워크스페이스를 하나씩 실행하세요.

```bash
npm test                       # every workspace's test script

npm -w @nodaro/shared test     # pure-logic unit tests
npm -w @nodaro/sdk test        # SDK contract tests against a mocked API
cd backend && npm test         # route and service tests
cd frontend && npm test        # component and hook tests
```

테스트할 대상:

- **API 라우트**: 기본 경로와, 스키마에 나열된 경계 사례를 테스트합니다. Supabase와 AI 공급자는 모의 객체로 대체하세요. 단위 테스트는 실제 공급자 API를 절대 호출하지 않습니다. 대부분의 기존 테스트에 복사해 쓸 수 있는 설정이 있습니다.
- **프런트엔드 컴포넌트**: React Testing Library로 스모크 테스트를 합니다. 무거운 로직은 따로 단위 테스트할 수 있도록 훅이나 헬퍼에 두고, React Flow나 Zustand 스토어의 내부는 테스트하지 마세요.
- **공유 패키지**: 순수 함수 단위 테스트입니다. 패키지의 내보내기는 프런트엔드와 백엔드 사이에서 직렬화할 수 있어야 합니다.

CI는 `tsc --noEmit`, Vitest, 간단한 린트 검사를 실행합니다. 로컬에서는 실패하는데 CI에서는 통과하거나 그 반대라면, 재현 단계를 적어 이슈를 여세요.

### ffmpeg 출력 검사
일반 단위 테스트는 ffmpeg의 렌더링 결과가 아니라 ffmpeg에 전달되는 인수를 검사합니다. 별도의 특성화 스위트는 ffmpeg 기반의 모든 작업으로 테스트 픽스처를 렌더링합니다. 그런 다음 디코딩한 출력에서 측정한 속성을 커밋된 기준값과 비교합니다. 비교하는 속성은 에너지, 스펙트럼, 감쇠, 길이, 프레임별 밝기입니다.

이 스위트는 `npm test`에서 빠져 있습니다. 측정값이 프로덕션 이미지에 고정된 정확한 ffmpeg 빌드에서만 유효하기 때문입니다. 그 이미지 안에서 실행하세요.

```bash
backend/scripts/characterize-in-image.sh check   # compare with the reference values
backend/scripts/characterize-in-image.sh bless   # rewrite the reference values, on purpose only
cd backend && npm run characterize:report -- --against ffmpeg-X.json
```

ffmpeg 기반 작업을 바꿨다면 풀 리퀘스트를 열기 전에 `check`를 실행하세요. CI도 실행합니다. 렌더링 결과를 바꾸려는 변경이라면 이미지 안에서 bless를 실행하고 새 기준값을 커밋하되, 바뀐 측정값마다 설명을 붙이세요. 기준값을 손으로 편집하지 말고, 로컬 ffmpeg 빌드로 bless하지 마세요. 스위트의 버전 검사가 이를 거부합니다.

## 노드나 모델 공급자 추가
노드를 추가하면 API 라우트, 프런트엔드 컴포넌트, 실행기, 여러 레지스트리 등 많은 파일을 건드리게 됩니다. 하나라도 빠뜨리면 노드가 피커 중 하나에 나타나지 않거나 **실행** 버튼이 아무 동작도 하지 않는 등 헷갈리는 결과가 생깁니다. `CLAUDE.md`의 **New Node Registration**(새 노드 등록) 체크리스트를 단계별로 따르세요.

기존 노드에 모델을 추가하는 일은 더 짧으며, `CLAUDE.md`에 전용 **Provider Enum Sync**(공급자 열거형 동기화) 체크리스트가 있습니다. 가장 자주 빠뜨리는 단계는 API 라우트의 스키마입니다. 스키마를 고치지 않으면 편집기가 API가 `400`으로 거부하는 옵션을 보여 주게 됩니다.

## Changesets
npm에 공개되는 패키지는 `@nodaro/shared`, `@nodaro/sdk`, `@nodaro/cli` 세 개입니다. 릴리스는 Changesets를 쓰며 자동화되어 있습니다. 변경 사항이 이 중 하나를 건드리면 다음을 실행하세요.

```bash
npx changeset
```

패키지, 버전 올림 유형(patch, minor, major), 한 줄 요약을 선택하세요. `.changeset/` 아래에 생성된 파일을 풀 리퀘스트와 함께 커밋하세요. **Changeset Guard** 검사는 changeset 없이 공개 패키지를 바꾼 풀 리퀘스트를 실패시킵니다. 릴리스 노트가 필요 없는 변경이면 `npx changeset --empty`를 쓰세요.

나머지는 모두 자동입니다. `dev`에서는 **Version Packages** 풀 리퀘스트가 대기 중인 changeset을 모읍니다. `dev`가 `main`으로 승격되면 릴리스 워크플로가 새 버전을 npm에 공개하고, 태그를 달고, GitHub 릴리스를 만들고, 독립 실행형 CLI 바이너리를 다시 빌드합니다. `backend`, `frontend`, Remotion 워크스페이스는 공개되지 않으므로 changeset이 필요 없습니다.

## 행동 강령
친절하고, 서로 존중하고, 선의를 전제하세요. 이 프로젝트는 [CODE_OF_CONDUCT.md](https://github.com/nodaroai/app.nodaro.ai/blob/main/CODE_OF_CONDUCT.md)에 있는 Contributor Covenant 2.1을 따릅니다. 어떤 종류든 괴롭힘은 프로젝트에서 제외되는 사유가 됩니다.

- 사람이 아니라 코드를 비판하세요.
- 무례하지 않게 반대 의견을 내세요. “Y 때문에 여기서는 패턴 X가 더 깔끔할 것 같습니다”는 괜찮지만, “이건 별로입니다”는 안 됩니다.
- 처음 기여하는 사람을 리뷰하는 메인테이너는 인내심을 가져 주세요.

## 질문할 곳
- **GitHub Discussions**: 열린 질문, 아이디어, Nodaro로 만든 것을 나누는 곳입니다. 버그가 아닌 것은 Issues보다 이곳을 쓰세요.
- **GitHub Issues**: 버그 신고와 기능 요청.
- **보안 문제**: GitHub의 비공개 보안 권고로 신고하세요.

프로젝트에 관해 메인테이너에게 개인적으로 이메일을 보내지 마세요. 공개된 답변은 모두에게 도움이 됩니다. 보안 신고와 이해 상충 문제는 개인적으로 연락해도 됩니다.

## 라이선스와 기여자 계약
리포지토리에는 네 가지 라이선스 등급이 있습니다. 대부분의 코드는 Nodaro Sustainable Use License를 따릅니다. 모든 `ee` 폴더 안의 코드와 이름에 `.ee.`가 들어간 파일은 Nodaro Enterprise License를 따릅니다. `@nodaro/prompts`는 FSL-1.1-Apache-2.0을, `@nodaro/sdk`, `@nodaro/shared`, `@nodaro/cli`는 Apache 2.0을 따릅니다. [라이선스](https://nodaro.ai/docs/self-hosting/license)를 참고하세요.

**새 코드를 둘 곳.** Apache 패키지는 공개되는 버전마다 취소할 수 없는 권리 부여가 됩니다. 새로운 프롬프트 엔지니어링, 카탈로그, 프리셋은 `@nodaro/prompts`나 `backend/`에 둡니다. `@nodaro/shared`에는 공개 API와 SDK 계약에 필요한 것, 즉 타입, 와이어 열거형, API 사용자와 공유하는 검증 같은 것이나, 재사용을 위해 의도적으로 공개하는 것만 추가하세요. 둘 중 어느 쪽인지 풀 리퀘스트에 적어 주세요.

**기여자 계약.** 기여를 제출하면 [Nodaro Contributor License Agreement](https://github.com/nodaroai/app.nodaro.ai/blob/main/CLA.md)에 동의하는 것입니다. 개인 기여와 회사 기여 모두 같은 계약이 적용되며, 계약의 2조가 고용주의 허락을 다룹니다. 첫 풀 리퀘스트에서 cla-assistant 봇이 서명을 요청합니다. 고용주를 대신해 기여한다면 고용주의 지식 재산권 정책이 이를 허용하는지 확인하세요. 이 계약에 따라 Nodaro는 내 기여를 자사의 어떤 라이선스로든 제공할 수 있으며, Sustainable Use License와 Enterprise License 사이에서 옮기는 것도 포함됩니다.

## 자주 묻는 질문

### 풀 리퀘스트는 어느 브랜치를 대상으로 해야 하나요?

dev에서 브랜치를 만들고 dev로 풀 리퀘스트를 여세요. main에서 브랜치를 만들거나 main에 직접 커밋하지 마세요. 변경 사항은 스테이징 인스턴스에서 충분히 검증된 뒤 main에 반영됩니다.

### 기여자 라이선스 계약에 서명해야 하나요?

네. 기여를 제출하면 Nodaro Contributor License Agreement에 동의하는 것입니다. 첫 풀 리퀘스트에서 cla-assistant 봇이 서명을 요청하며, 개인 기여와 회사 기여 모두 같은 계약이 적용됩니다.

### 테스트는 어떻게 실행하나요?

리포지토리 루트에서 npm test를 실행하면 모든 워크스페이스의 Vitest 스위트가 실행됩니다. npm -w @nodaro/shared test처럼 워크스페이스 하나만 실행할 수도 있습니다. 커밋하기 전마다 npx tsc --noEmit으로 backend와 frontend의 타입을 검사하세요.

### changeset은 언제 필요한가요?

변경 사항이 공개 패키지인 @nodaro/shared, @nodaro/sdk, @nodaro/cli를 건드릴 때 필요합니다. npx changeset을 실행하고 생성된 파일을 커밋하세요. 릴리스 노트가 필요 없는 변경이면 npx changeset --empty를 쓰세요.

### 기여에 관한 질문은 어디에서 하나요?

자유로운 질문과 아이디어는 GitHub Discussions에, 버그와 기능 요청은 GitHub Issues에 올리세요. 보안 문제는 비공개 보안 권고로 신고하세요.
