Files
SnapOtter/apps/docs/ko/guide/developer.md
T
SnapOtterandGitHub d10d0f544f fix: release QA hardening across processing, media, security, and CI gates (#649)
A release-readiness QA pass over the whole product. The commits split into
defects a user would hit and gates that were reporting green while measuring
nothing.

## Fixes that change behaviour

Rate limiting was bypassable on every install: TRUST_PROXY defaulted to true, so
request.ip came from a client-set header and a forged X-Forwarded-For got past
the login limiter. The default is now a private-network trust list.

A transient Postgres outage stranded in-flight jobs, leaving finished output on
disk with no row pointing at it. A reconciler now resolves those rows and adopts
the bytes rather than dropping the work.

A Redis connection that moved to a new address wedged every read-blocked
consumer, so completions stopped signalling while health still answered 200.
Socket timeouts plus subscriber pings recover it.

Installing more than one AI bundle left the shared venv multi-versioned and
silently broke three tools. The installer now reconciles distributions to one
version each.

Converting an image to JXL at quality 1 through 4 returned a 500, because
libjxl 0.7 rejects the distance those values compute. The quality is floored at
what the encoder honours. A missing ffmpeg was also reported to the user as a
corrupt upload; it now says the engine is unavailable.

RAW uploads reached an unpatched LibRaw on arm64, so it is built from source at
0.22.2, and the release scan was split so it can fail on an unfixed critical
instead of hiding it behind ignore-unfixed.

## Gates that could not fail

Two mutation lanes ran zero mutants because Stryker crawled the gitignored docs
build; coverage discarded its whole report on any failing test; the lint gate
skipped root tests, scripts, and two workspaces; and several generated matrices
counted a host missing ffmpeg as a passing tool. Each now measures what it
claims.

Full evidence and the outstanding release items are tracked locally and are not
part of this branch.
2026-07-27 15:37:30 +08:00

9.5 KiB

description, i18n_source_hash, i18n_provenance, i18n_output_hash, i18n_hash_version
description i18n_source_hash i18n_provenance i18n_output_hash i18n_hash_version
SnapOtter의 로컬 개발 환경 설정, 명령어, 코드 규칙, 새 도구를 추가하는 방법. 56acc1bf9a9b machine 6df1aea31133 2

개발자 가이드

로컬 개발 환경을 설정하고 SnapOtter에 코드를 기여하는 방법입니다.

사전 요구사항

  • Node.js 22.22+
  • pnpm 9+ (corepack enable && corepack prepare pnpm@latest --activate)
  • Docker (로컬 Postgres + Redis, 컨테이너 빌드, AI 기능에 필요)
  • Git

Python 3.11+는 AI/ML 사이드카(배경 제거, 업스케일링, OCR)를 작업하는 경우에만 필요합니다.

설정

git clone https://github.com/snapotter-hq/snapotter.git
cd snapotter
docker compose -f docker-compose.dev.yml up -d   # start Postgres + Redis
pnpm install
pnpm dev

이것은 두 개의 개발 서버를 시작합니다:

서비스 URL 비고
프론트엔드 http://localhost:1351 Vite 개발 서버, /api 프록시
백엔드 http://localhost:13490 Fastify API (프록시를 통해 접근)

브라우저에서 http://localhost:1351를 여세요. admin / admin으로 로그인하세요. 첫 로그인 시 비밀번호를 변경하라는 안내가 표시됩니다.

프로젝트 구조

apps/
  api/              Fastify backend
  web/              Vite + React frontend
  docs/             VitePress documentation (this site)
packages/
  shared/           Constants, types, i18n strings
  image-engine/     Sharp-based image operations
  media-engine/     FFmpeg spawn + progress parsing
  doc-engine/       qpdf, LibreOffice, ghostscript wrappers
  ai/               Python sidecar bridge for ML models
tests/
  unit/             Vitest unit tests
  integration/      Vitest integration tests (full API)
  e2e/              Playwright end-to-end specs
  fixtures/         Small test images

명령어

pnpm dev                # start frontend + backend
pnpm build              # build all workspaces
pnpm typecheck          # TypeScript check across monorepo
pnpm lint               # Biome lint + format check
pnpm lint:fix           # auto-fix lint + format
pnpm test               # unit + integration tests
pnpm test:unit          # unit tests only
pnpm test:integration   # integration tests only
pnpm test:e2e           # Playwright e2e tests
pnpm test:coverage      # tests with coverage report

코드 규칙

  • 큰따옴표, 세미콜론, 2칸 들여쓰기 (Biome로 강제)
  • 모든 워크스페이스에서 ES 모듈
  • semantic-release를 위한 Conventional commits
  • 모든 API 입력 검증에 Zod 사용
  • Biome, TypeScript, 에디터 설정 파일 수정 금지. 린터가 아니라 코드를 고치세요.

데이터베이스

Drizzle ORM(pg-core)을 통한 PostgreSQL 17. 로컬 개발에는 Postgres와 Redis 실행이 필요합니다. 다음으로 시작하세요:

docker compose -f docker-compose.dev.yml up -d

이것은 5432 포트의 Postgres와 6379 포트의 Redis를 제공합니다. 그런 다음 마이그레이션을 생성하고 적용하세요:

cd apps/api
npx drizzle-kit generate   # generate a migration from schema changes
npx drizzle-kit migrate    # apply pending migrations

스키마는 apps/api/src/db/schema.ts에 정의되어 있습니다. 테이블: users, sessions, settings, jobs, apiKeys, pipelines, teams, userFiles, roles, auditLog.

새 도구 추가하기

모든 도구는 동일한 패턴을 따릅니다. 다음은 최소 예시입니다.

1. 백엔드 라우트

apps/api/src/routes/tools/my-tool.ts를 생성하세요:

import { z } from "zod";
import type { FastifyInstance } from "fastify";
import { createToolRoute } from "../tool-factory.js";

const settingsSchema = z.object({
  intensity: z.number().min(0).max(100).default(50),
});

export function registerMyTool(app: FastifyInstance) {
  createToolRoute(app, {
    toolId: "my-tool",
    settingsSchema,
    async process(inputBuffer, settings, filename) {
      // Use sharp or other libraries to process the image
      const sharp = (await import("sharp")).default;
      const result = await sharp(inputBuffer)
        // ... your processing logic
        .toBuffer();

      return {
        buffer: result,
        filename: filename.replace(/\.[^.]+$/, ".png"),
        contentType: "image/png",
      };
    },
  });
}

그런 다음 apps/api/src/routes/tools/index.ts에 등록하세요.

2. 프론트엔드 설정 컴포넌트

apps/web/src/components/tools/my-tool-settings.tsx를 생성하세요:

import { useState } from "react";
import { useToolProcessor } from "@/hooks/use-tool-processor";
import { useFileStore } from "@/stores/file-store";

export function MyToolSettings() {
  const { files } = useFileStore();
  const { processFiles, processing, error, downloadUrl } =
    useToolProcessor("my-tool");

  const [intensity, setIntensity] = useState(50);

  const handleProcess = () => {
    processFiles(files, { intensity });
  };

  return (
    <div className="space-y-4">
      {/* your controls here */}
      <button
        type="button"
        onClick={handleProcess}
        disabled={files.length === 0 || processing}
        data-testid="my-tool-submit"
        className="w-full py-2.5 rounded-lg bg-primary text-primary-foreground font-medium disabled:opacity-50"
      >
        Process
      </button>
    </div>
  );
}

그런 다음 apps/web/src/lib/tool-registry.tsx의 프론트엔드 도구 레지스트리에 등록하세요:

// Add the lazy import
const MyToolSettings = lazy(() =>
  import("@/components/tools/my-tool-settings").then((m) => ({
    default: m.MyToolSettings,
  })),
);

// Add to the toolRegistry Map
["my-tool", { displayMode: "before-after", Settings: MyToolSettings }],

표시 모드: "side-by-side", "before-after", "live-preview", "no-comparison", "interactive-crop", "interactive-eraser", "no-dropzone".

3. i18n 항목

packages/shared/src/i18n/en.ts에 추가하세요:

"my-tool": {
  name: "My Tool",
  description: "Short description of what this tool does",
},

4. 테스트

e2e 테스트가 안정적으로 대상을 지정할 수 있도록 액션 버튼에 data-testid 속성을 추가하세요(위에서 보인 대로).

Docker 빌드

전체 프로덕션 이미지를 로컬에서 빌드하세요:

docker build -f docker/Dockerfile -t snapotter:latest .

더 빠른 재빌드를 위해 BuildKit 캐시 마운트를 사용하세요:

DOCKER_BUILDKIT=1 docker build -f docker/Dockerfile -t snapotter:latest .

릴리스 버전 도메인

SnapOtter에는 의도적으로 세 가지 버전 도메인이 있습니다. 릴리스 중에는 한 도메인을 다른 도메인으로 복사하지 마십시오.

  • 애플리케이션 릴리스 버전에는 루트 매니페스트, 모든 개인 작업 공간 패키지 및 APP_VERSION가 포함됩니다. Semantic-release는 이 값을 제공하고 pnpm version:sync <version>는 애플리케이션 릴리스 전에 모든 작업 공간을 업데이트합니다.
  • OpenAPI info.version는 안정적인 공개 API 주요 계약입니다. 모든 현지화된 사양은 호환되는 애플리케이션 릴리스를 위해 <major>.0.0에 유지되며 API 계약이 새로운 주요 버전으로 이동하는 경우에만 변경됩니다.
  • docker/feature-manifest.jsonimageVersion: 2.0.0를 불변의 레거시 기능 번들 스토리지 시대로 유지합니다. 해당 v2 아카이브 경로는 애플리케이션 패키지 버전이 아닙니다. Accurate OCR은 런타임 형식 v3을 사용하고 애플리케이션 릴리스 출처를 별도로 기록합니다.

tests/unit/infra/release-version-policy.test.ts는 이러한 경계를 적용합니다. 새 버전 도메인 또는 마이그레이션은 해당 계약과 관련 아티팩트 마이그레이션 설계를 함께 업데이트해야 합니다. 독립적인 API 및 레거시 번들 값은 config/release-version-policy.json에 있습니다. 애플리케이션 버전 동기화는 해당 정책 파일을 암시적으로 다시 작성해서는 안 됩니다.

환경 변수

전체 목록은 구성 가이드를 참고하세요. 개발에 중요한 것들:

변수 기본값 설명
AUTH_ENABLED true 인증 활성화/비활성화
DEFAULT_USERNAME admin 기본 관리자 사용자명
DEFAULT_PASSWORD admin 기본 관리자 비밀번호
SKIP_MUST_CHANGE_PASSWORD false 강제 비밀번호 변경 건너뛰기 (CI/개발 전용)
RATE_LIMIT_PER_MIN 1000 분당 API 속도 제한 (0 = 비활성화)
MAX_UPLOAD_SIZE_MB 100 MB 단위 최대 업로드 크기 (0 = 무제한)