Ctrl K
ComunidadGuías…

CONTRIBUTING.md

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
# Contributing

Thanks for your interest in Secret. This guide covers setting up the project,
what a change needs before it's merged, and where things go. Releases are
described for maintainers in [docs/releasing.md](docs/releasing.md).

## Prerequisites

- **Node.js 26 or later.** The code uses `node:sqlite` and `Error.isError`.
  Check with `node --version`; with nvm, run `nvm use 26`.
- **pnpm 10**, through corepack, which picks up the version pinned in
  `package.json`. Node 25 and later don't ship corepack: install the version
  the Dockerfile uses, `npm install -g corepack@0.35.0 && corepack enable`.
- **Docker** (optional), for the containerized workflow below.

## Getting started

```bash
git clone https://github.com/largerio/secret.git
cd secret
pnpm install

# The API reads its config from the shell environment (there is no dotenv
# loader), so export a key before starting it:
export SERVER_ENCRYPTION_KEY=$(openssl rand -base64 32)

pnpm dev
```

This starts the API on `http://localhost:3000` and the web app on
`http://localhost:5173`, whose dev server sends `/api` to the API. `PORT`
moves the API, and the dev server follows it.

The key must decode to exactly 32 bytes, or the server refuses to start. Keep
the same key across restarts: the notes you created earlier can't be read
with another one, and the server refuses to start rather than ignore it.

### With Docker

The `Makefile` wraps `docker-compose.dev.yml`, needs no local Node install,
and ships a working dev key:

```bash
make dev          # API + web with live reload
make check        # lint + typecheck + test
make shell        # a shell inside the dev container
```

## Making a change

1. Create a branch from `main`.
2. Make your change, with tests. New behavior without tests will be asked
   for them.
3. Run the checks:

   ```bash
   pnpm lint            # Biome lint + format
   pnpm typecheck       # TypeScript strict
   pnpm test:coverage   # the whole suite, with the 100% coverage gate CI enforces
   ```

4. Update the docs that describe what you changed:
   - a change people will notice in the app: an entry under `[Unreleased]`
     in [CHANGELOG.md](CHANGELOG.md);
   - a change to the SDK or the command line: a changeset, with
     `pnpm changeset` ([npm packages](docs/releasing.md#npm-packages));
   - a change to the note format, the payload, a read or upload endpoint, a
     limit a client must respect, or a new error code:
     [PROTOCOL.md](PROTOCOL.md), with new test vectors when the bytes change.
5. Commit with a clear message (see [Commit messages](#commit-messages)), and
   open a pull request.

## Code style

We use [Biome](https://biomejs.dev/) for linting and formatting:

- **Indentation**: tabs
- **Line width**: 100 characters
- **Quotes**: double quotes
- **Semicolons**: always

Run `pnpm lint:fix` to fix most issues.

### TypeScript

Strict mode is on, with these additional flags:

- `noUncheckedIndexedAccess`: indexing an array or object returns `T | undefined`
- `exactOptionalPropertyTypes`: `undefined` must be explicit
- `noNonNullAssertion`: no `!` assertions
- `noExplicitAny`: no `any` type (enforced by Biome)

Use `import type { ... }` for type-only imports.

## Project structure

```
apps/api/           Backend (Hono, Node.js 26, SQLite), which also serves the web app
apps/web/           Frontend (SvelteKit, Svelte 5, Tailwind CSS 4), built as static files
apps/e2e/           Playwright end-to-end tests (incl. the axe-core a11y gate)
packages/sdk-js/    JS/TS SDK (SecretClient, encrypt/decrypt flows)
packages/cli/       Command line (`secret`), built on the SDK
packages/crypto/    Encryption library (libsodium, AES-256-GCM)
packages/shared/    Shared types, Zod schemas, constants, crypto test vectors
messages/           i18n translations (10 languages)
```

### Where to put things

- **New API endpoint**: `apps/api/src/routes/`
- **New validation schema**: `packages/shared/src/validation.ts`
- **New type**: `packages/shared/src/types.ts` (export it from `index.ts`)
- **New constant**: `packages/shared/src/constants.ts` (export it from `index.ts`)
- **New SDK feature**: `packages/sdk-js/src/`
- **New crypto function**: `packages/crypto/src/`
- **New UI component**: `apps/web/src/lib/components/`
- **New translation key**: every file in `messages/` (see [i18n](#i18n))
- **Storage backend**: implement the `StorageBackend` interface in `apps/api/src/storage/`

## Testing

We use [Vitest](https://vitest.dev/). Tests live in `__tests__/` directories
next to the source.

```bash
pnpm test              # run once
pnpm test:watch        # watch mode
pnpm test:coverage     # with the coverage gate
```

### Writing tests

- **API routes**: use `app.request()` (Hono's test helper, no HTTP server)
- **Database**: use real SQLite files, cleaned up in `beforeEach`
- **Crypto**: call `await initSodium()` in `beforeAll`
- **S3**: mock `@aws-sdk/client-s3` with class-based mocks (see `s3.test.ts`)

### End-to-end tests

```bash
pnpm --filter @largerio/e2e exec playwright install --with-deps chromium   # the first time
pnpm test:e2e
```

The suite builds the web app and starts the API serving it, as the image
does. CI runs it against the image instead, on S3 storage:
`.github/scripts/e2e-image.sh <image>` starts both, then
`E2E_BASE_URL=http://localhost:3000 pnpm test:e2e` targets them. With the web
app built (`pnpm --filter @largerio/web build`),
`scripts/verify-web-app.sh http://localhost:3000` then checks that the image
serves that very build, as CI does, and as anyone can of an instance
([SECURITY.md](SECURITY.md#verifying-the-web-app)).

The command line runs against the same image, installed with npm from its
packed tarball and the SDK's, as a user installs it. Start the image with an
API key of at least 32 characters in `E2E_API_KEY`, build the SDK and the CLI
(`pnpm --filter @largerio/secret-sdk --filter @largerio/secret-cli build`),
then run `.github/scripts/e2e-cli.sh http://localhost:3000` with the same
`E2E_API_KEY`. CI runs it on Node 22, the oldest the CLI supports.

## Commit messages

Follow [Conventional Commits](https://www.conventionalcommits.org/):

```
feat: add S3 storage backend
fix: timing-safe delete token comparison
test: add multipart upload endpoint tests
docs: update README with S3 configuration
refactor: extract storage abstraction layer
```

## Security

Secret is built around one promise: the server never sees what people send.
Please keep in mind:

- **Never log or expose plaintext content.** The server must stay
  zero-knowledge.
- **Validate every input** with Zod schemas; never trust client data.
- **Compare tokens and secrets in constant time** (`crypto.timingSafeEqual`).
- **Zero keys after use** with `sodium.memzero()`.
- **No `eval` or dynamic code execution**: the CSP is strict.
- **Never leak internal details in errors.** An error answers problem details
  with a stable `code`, listed in [PROTOCOL.md](PROTOCOL.md#errors).

If you find a vulnerability, report it privately: see
[SECURITY.md](SECURITY.md#reporting-a-vulnerability).

## i18n

Translations live in `messages/*.json` (10 languages: en, fr, es, de, pt, it,
ja, zh, ru, ko).

### Adding a string

1. Add the key to **every** language file: `key-parity.test.ts` fails
   otherwise.
2. Use `t("key_name")` in Svelte components.
3. Use `t("key_name", { param: value })` for interpolation.

### Adding a language

Add its JSON file to `messages/`, then register it in four places:

1. `packages/shared/src/site.ts`: add the code to `LOCALES`, which the server
   picks a visitor's language from.
2. `apps/web/src/lib/i18n/index.svelte.ts`: import the file and add it to the
   `messages` record.
3. `apps/web/src/lib/components/LangMenu.svelte`: add it to `LANGS`.
4. `apps/api/src/site/shell.ts`: add its `og:locale` to `OG_LOCALES`.

The last three are keyed by `Locale`: once the code is in `LOCALES`,
`pnpm typecheck` names whichever of them still lacks it. The sitemap and the
pages' `hreflang` links follow `LOCALES` on their own.

The API reads every language's file at startup, to write the pages' heads,
and refuses to start when `en.json` lacks one of the messages they need
(`HEAD_MESSAGES` in `apps/api/src/site/messages.ts`). The other languages
fall back to English, message by message.

## Dependencies

- pnpm resolves no version published less than two days ago
  (`minimumReleaseAge` in `pnpm-workspace.yaml`), so a compromised release has
  time to be yanked before it reaches the lockfile. To take an urgent fix
  sooner, list its package under `minimumReleaseAgeExclude` for that update.
- Dependabot proposes no npm package or action less than three days old. Its
  npm patch and minor PRs, and its bumps of the base image digest in the
  Dockerfiles, are labeled `auto-merge-48h` and merged once they're 48 hours
  old and green. Majors and GitHub Actions updates are reviewed and merged by
  hand: an action runs with the tokens and secrets of the workflows that use
  it.

## License

By contributing, you agree that your contributions will be licensed under the
[MIT License](LICENSE).