# Contributing

> Contribute code to Nodaro on GitHub. Prepare the development environment, branch from dev, run the tests, add a changeset and sign the contributor agreement.

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

**Contributing** to Nodaro means sending code to the public repository, [github.com/nodaroai/app.nodaro.ai](https://github.com/nodaroai/app.nodaro.ai), as a pull request. Nodaro is source-available under the Nodaro Sustainable Use License. This page covers the development setup, branches, pull requests, tests and the contributor agreement.

To run Nodaro on your own server instead, start with the [Quickstart](https://nodaro.ai/docs/self-hosting/quickstart). To build on the API, see the [SDK](https://nodaro.ai/docs/developers/sdk).

## The repository

The repository is one npm workspaces monorepo:

| Folder | What lives there |
| --- | --- |
| `backend/` | The Fastify API, the BullMQ workers and the orchestrator. Node.js 22, TypeScript. |
| `frontend/` | The Vite app: the editor, the app runner and the admin panel. React 19, React Router 7, React Flow. |
| `packages/` | `@nodaro/shared`, pure logic shared across the stack; `@nodaro/sdk`, the typed REST client; `@nodaro/cli`; `@nodaro/prompts`, the prompt layer; and the Remotion video compositions. |
| `supabase/` | The database schema, as forward-only SQL migrations. |
| `docs/` | The documentation in the repository, including design notes that explain the reasons behind key features. |
| `scripts/` | Repository utilities, such as the architecture graph generator and audits. |
| `.changeset/` | Pending version bumps for the published packages. |

The house rules are in `CLAUDE.md` at the repository root: coding standards, the checklist for adding a model provider and the checklist for adding a node. Read it before a non-trivial pull request. [Architecture](https://nodaro.ai/docs/self-hosting/architecture) explains how the pieces fit together.

## Prepare the development environment

You need Node.js 22 or later, with npm.

### Clone and install

```bash
git clone https://github.com/nodaroai/app.nodaro.ai
cd app.nodaro.ai
npm install        # installs every workspace
```

### Configure the environment

```bash
cp .env.example .env
```

Set at least `SUPABASE_URL`, `SUPABASE_SERVICE_ROLE_KEY`, `SUPABASE_ANON_KEY`, `INTERNAL_ORCHESTRATOR_SECRET` and one provider key, such as `KIE_API_KEY`, `REPLICATE_API_TOKEN` or `ANTHROPIC_API_KEY`. Generate the secrets:

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

The easiest database is a free project on supabase.com. A fully local stack with the Supabase CLI, `supabase start`, also works but takes more setup. Workflow runs also need Redis, at `REDIS_URL`, which defaults to `redis://localhost:6379`. `.env.example` lists every supported variable, and [Configuration](https://nodaro.ai/docs/self-hosting/configuration) explains them.

### Build the shared package once

The frontend reads `@nodaro/shared` from its build output, so build it before the first start. Afterwards, the package's own dev script rebuilds it when you edit shared code.

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

### Start the dev servers

In two terminals:

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

## Coding standards

The most important rules from `CLAUDE.md`:

- **File size.** 200 to 400 lines is typical, and 800 is the hard ceiling. Split a file that grows larger.
- **No `console.log` in production code.** Use the existing logger patterns.
- **Conventional commits:** `feat:`, `fix:`, `refactor:`, `docs:`, `chore:`, `test:`, with a specific subject line.
- **Type-check before every commit**, with `npx tsc --noEmit` in both `backend/` and `frontend/`. CI blocks pull requests that do not type-check.
- **Fastify plugins, not Express routers.** Every route file exports an async function that takes the Fastify instance.
- **Every API endpoint has a Zod schema**, with no exceptions. The schema validates the request and also produces the OpenAPI document.
- **Frontend state:** React Query for server state, Zustand for interface state and React Flow for canvas state. Do not mix them.
- **Never mutate objects or arrays.** Always create new copies: Zustand and React Flow both rely on reference equality.

## Branches and pull requests

- The repository has two long-lived branches: `dev`, for staging, and `main`, for production.
- Fork the repository, **branch from `dev`**, and open your pull request into `dev`. Never branch from `main` or commit to it directly.
- Name the branch by its type: `feat/`, `fix/`, `refactor/`, `docs/`, `chore/` or `test/`, for example `feat/whisper-tts-node`.
- Merged changes run first on the staging instance, `next.nodaro.ai`. After a soak of about 24 hours, a maintainer promotes `dev` to `main`.

In your pull request:

- Link the GitHub issue it relates to.
- Add screenshots or GIFs for changes to the interface.
- Follow the node checklist in `CLAUDE.md` when you add or change a node.
- Add a changeset when you change a published package. See [Changesets](#changesets).

## Tests

Each workspace has its own Vitest suite. Run everything from the repository root, or one workspace at a time:

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

What to test:

- **API routes** — the main path, plus the edge cases your schema lists. Mock Supabase and the AI providers: a unit test never calls a real provider API. Most existing tests have a setup you can copy.
- **Frontend components** — smoke tests with React Testing Library. Put heavy logic in hooks or helpers, where it can be unit-tested alone, and do not test the internals of React Flow or the Zustand stores.
- **The shared package** — pure-function unit tests. Its exports must stay serializable between the frontend and the backend.

CI runs `tsc --noEmit`, Vitest and a small lint pass. If a test fails locally but passes in CI, or the reverse, open an issue with the steps to reproduce it.

### ffmpeg output checks

Ordinary unit tests check the arguments passed to ffmpeg, not what ffmpeg renders. A separate characterization suite renders test fixtures through every ffmpeg-backed operation. It then compares measured properties of the decoded output with committed reference values: energy, spectrum, decay, duration and per-frame brightness.

The suite is left out of `npm test`: its numbers are valid only against the exact ffmpeg build pinned in the production image. Run it inside that image:

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

If you change an ffmpeg-backed operation, run `check` before you open the pull request. CI runs it too. If your change is meant to alter the rendered output, bless inside the image and commit the new reference values, with an explanation of every measure that moved. Never edit reference values by hand, and never bless against a local ffmpeg: the suite's version guard rejects it.

## Add a node or a model provider

Adding a node touches many files: an API route, a frontend component, the executor and several registries. Missing one gives confusing results, such as a node that does not appear in one of the pickers or a **Run** button that does nothing. Follow the **New Node Registration** checklist in `CLAUDE.md`, step by step.

Adding a model to an existing node is a shorter job, with its own **Provider Enum Sync** checklist in `CLAUDE.md`. The step most often forgotten is the schema of the API route: without it, the editor offers an option that the API rejects with `400`.

## Changesets

Three packages publish to npm: `@nodaro/shared`, `@nodaro/sdk` and `@nodaro/cli`. Releases use Changesets and are automated. When your change touches one of them:

```bash
npx changeset
```

Choose the packages, the bump type (patch, minor or major) and a one-line summary. Commit the file it writes under `.changeset/` with your pull request. The **Changeset Guard** check fails pull requests that change a published package without one. For a change that needs no release note, use `npx changeset --empty`.

Everything else is automatic. On `dev`, a **Version Packages** pull request collects the pending changesets. When `dev` is promoted to `main`, the release workflow publishes the new versions to npm, tags them, creates the GitHub releases and rebuilds the standalone CLI binaries. The `backend`, `frontend` and Remotion workspaces never publish and need no changeset.

## Code of conduct

Be kind, be respectful and assume good faith. The project follows the Contributor Covenant 2.1, in [CODE_OF_CONDUCT.md](https://github.com/nodaroai/app.nodaro.ai/blob/main/CODE_OF_CONDUCT.md). Harassment of any kind is grounds for removal from the project.

- Critique code, not people.
- Disagree without being rude: "I think pattern X would be cleaner here because Y" works, "this is bad" does not.
- Maintainers reviewing a first-time contributor: be patient.

## Where to ask

- **GitHub Discussions** — open questions, ideas and things you built with Nodaro. Prefer it to Issues for anything that is not a bug.
- **GitHub Issues** — bug reports and feature requests.
- **Security problems** — report them through a private security advisory on GitHub.

Avoid emailing maintainers privately about the project: public answers help everyone. Private contact is fine for security reports and conflicts of interest.

## License and contributor agreement

The repository has four license tiers. Most code is under the Nodaro Sustainable Use License. Code in any `ee` folder and in files with `.ee.` in their name is under the Nodaro Enterprise License. `@nodaro/prompts` is under FSL-1.1-Apache-2.0, and `@nodaro/sdk`, `@nodaro/shared` and `@nodaro/cli` are under Apache 2.0. See [License](https://nodaro.ai/docs/self-hosting/license).

**Where new code goes.** Every published version of the Apache packages is an irrevocable grant. New prompt engineering, catalogs and presets belong in `@nodaro/prompts` or in `backend/`. Add to `@nodaro/shared` only what the public API and SDK contract needs, such as types, wire enums and validation shared with API users, or what you publish on purpose for reuse. Say which in your pull request.

**The contributor agreement.** By submitting a contribution, you agree to the [Nodaro Contributor License Agreement](https://github.com/nodaroai/app.nodaro.ai/blob/main/CLA.md). The same agreement covers individual and corporate contributions: its section 2 deals with your employer's permission. The cla-assistant bot asks you to sign it on your first pull request. If you contribute on behalf of an employer, check that its intellectual property policy allows it. The agreement lets Nodaro license your contribution under any of its licenses, including moving it between the Sustainable Use License and the Enterprise License.

## Frequently asked questions

### Which branch should my pull request target?

Branch from dev and open your pull request into dev. Never branch from main or commit to it directly. Changes reach main after a soak on the staging instance.

### Do I have to sign a contributor license agreement?

Yes. By submitting a contribution you agree to the Nodaro Contributor License Agreement. The cla-assistant bot asks you to sign it on your first pull request, and the same agreement covers individual and corporate contributions.

### How do I run the tests?

Run npm test from the repository root to run every workspace's Vitest suite, or run one workspace, such as npm -w @nodaro/shared test. Type-check backend and frontend with npx tsc --noEmit before every commit.

### When do I need a changeset?

When your change touches a published package, @nodaro/shared, @nodaro/sdk or @nodaro/cli. Run npx changeset and commit the file it writes. Use npx changeset --empty when the change needs no release note.

### Where do I ask questions about contributing?

Use GitHub Discussions for open-ended questions and ideas, and GitHub Issues for bugs and feature requests. Report security problems through a private security advisory.
