Files
SnapOtter/apps/docs/guide/developer.md
T
Siddharth Kumar Sah 9f68960eda docs: rewrite README, add CONTRIBUTING.md, developer and translation guides
Rewrite README to remove AI writing patterns (em dashes, promotional
language, vague claims). Make Quick Start section explicit about default
credentials and forced password change. Add Contributing section linking
to CONTRIBUTING.md, developer guide, and translation guide.

Create CONTRIBUTING.md with issue guidelines, PR workflow, commit
conventions, and development setup. Add developer guide (dev setup,
project structure, how to add a tool) and translation guide (how the
i18n system works, step-by-step for adding a language) to VitePress
docs. Register both new pages in the docs sidebar.
2026-03-28 12:14:24 +08:00

6.1 KiB

Developer guide

How to set up a local development environment and contribute code to Stirling Image.

Prerequisites

  • Node.js 22+
  • pnpm 9+ (corepack enable && corepack prepare pnpm@latest --activate)
  • Docker (for container builds and AI features)
  • Git

Python 3.10+ is only needed if you are working on the AI/ML sidecar (background removal, upscaling, OCR).

Setup

git clone https://github.com/siddharthksah/Stirling-Image.git
cd Stirling-Image
pnpm install
pnpm dev

This starts two dev servers:

Service URL Notes
Frontend http://localhost:1349 Vite dev server, proxies /api
Backend http://localhost:13490 Fastify API (accessed via proxy)

Open http://localhost:1349 in your browser. Login with admin / admin. You will be prompted to change the password on first login.

Project structure

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
  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

Commands

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

Code conventions

  • Double quotes, semicolons, 2-space indentation (enforced by Biome)
  • ES modules in all workspaces
  • Conventional commits for semantic-release
  • Zod for all API input validation
  • No modifications to Biome, TypeScript, or editor config files. Fix the code, not the linter.

Database

SQLite via Drizzle ORM. The database file lives at ./data/stirling.db by default.

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

Schema is defined in apps/api/src/db/schema.ts. Tables: users, sessions, settings, jobs, apiKeys, pipelines, teams, userFiles.

Adding a new tool

Every tool follows the same pattern. Here is a minimal example.

1. Backend route

Create 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",
      };
    },
  });
}

Then register it in apps/api/src/routes/tools/index.ts.

2. Frontend settings component

Create 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>
  );
}

Then add the route and component to the tool registry in the frontend.

3. i18n entry

Add to packages/shared/src/i18n/en.ts:

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

4. Tests

Add a data-testid attribute to your action button (as shown above) so e2e tests can target it reliably.

Docker builds

Build the full production image locally:

docker build -f docker/Dockerfile -t stirling-image:latest .

Use BuildKit cache mounts for faster rebuilds:

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

Environment variables

See the Configuration guide for the full list. Key ones for development:

Variable Default Description
AUTH_ENABLED true Enable/disable authentication
DEFAULT_USERNAME admin Default admin username
DEFAULT_PASSWORD admin Default admin password
SKIP_MUST_CHANGE_PASSWORD false Skip forced password change (CI/dev only)
RATE_LIMIT_PER_MIN 100 API rate limit per minute
MAX_UPLOAD_SIZE_MB 100 Maximum upload size in MB