# Contributing to filex Thanks for considering a contribution. This is a small, opinionated codebase - before opening a sizeable PR please file an issue describing what you're about to do. - [Development setup](#development-setup) - [Workflow](#workflow) - [Branches](#branches) - [Commit messages](#commit-messages) - [Testing](#testing) - [Code style](#code-style) - [Docs](#docs) - [Screenshots](#screenshots) - [Release process](#release-process) --- ## Development setup Requirements: - Go 1.25+ (`backend/go.mod` declares 1.25.0; the images build on golang:1.25) - Node.js 20+ - pnpm 9+ - (optional) Docker, ffmpeg, ghostscript for thumbnail dev; an ONLYOFFICE Document Server (the `onlyoffice/documentserver` image) for office thumbnails and the apps' office engine - filex runs no LibreOffice ```bash git clone https://github.com/brf-tech/filex.git cd filemanager pnpm install # all workspace packages pnpm run dev # parallel: package watch + admin Vite dev server # In another shell - Go backend # once, on a fresh clone: the binary embeds these two directories, and # `go build` refuses a //go:embed pattern that matches nothing mkdir -p backend/embed/admin backend/embed/web touch backend/embed/admin/.placeholder backend/embed/web/.placeholder cd backend FILEX_LISTEN=127.0.0.1:5212 FILEX_DATA_DIR=./.dev-data go run ./cmd/filex serve ``` ⚠ `serve` takes its settings from the environment (or `--config`), not from flags: `--listen` and `--data-dir` are refused with `unknown flag`. The admin SPA is served by Vite at in dev mode and proxies `/api/*` to the Go server at `:5212`. For the embedded build (what ships in the binary), use `pnpm run build:all`. ### Running with hot-reload ```bash # Terminal 1 - Go (recompiles on save with air) go install github.com/air-verse/air@latest cd backend && air # Terminal 2 - admin SPA + packages pnpm run dev ``` --- ## Workflow 1. **Fork** + create a feature branch off `main`. 2. **Code** + write tests. 3. **Lint locally**: `pnpm run lint` and `cd backend && go vet ./... && staticcheck ./...`. 4. **Test locally**: `pnpm run test` and `cd backend && go test -race -timeout 30m ./...`. 5. **Open MR** against `main`. CI runs lint + test + build. 6. **Address review** + squash if asked. 7. **Merge** - the maintainers take your commits into their tree with your authorship kept, and the change reaches `main` with the next release: its export commit merges your pull request's head, so GitHub shows it merged then ([Release process](#release-process), step 7). Until that release is out the pull request stays open. The CHANGELOG says what it changed. --- ## Branches - `main` - protected, always green. - `feat/`, `fix/`, `chore/` - feature branches. - `sec/` - an embargoed security fix (a hole in a released version). It lives in the maintainers' private repository only, passes the full chain there (Testing -> The private pipeline) and reaches `main` with the patch release that ships it. - `release/v0.X.Y` - short-lived branch only used to cut a release. We don't run a `develop` branch. Trunk-based development with feature flags when something needs to land partially. --- ## Commit messages [Conventional Commits](https://www.conventionalcommits.org/). The CHANGELOG generator depends on the prefixes: ``` (): ``` Types we use: | Type | Meaning | |----------|----------------------------------------------------| | `feat` | new user-visible feature | | `fix` | bug fix | | `perf` | performance change with no behaviour change | | `refactor`| internal restructuring, no behaviour change | | `docs` | documentation only | | `test` | tests only | | `chore` | tooling, deps, CI; no functional change | | `ci` | CI config only | | `build` | build pipeline / Dockerfiles | Scopes (optional but encouraged): `backend`, `core`, `webcomponent`, `react`, `web`, `docker`, `ci`, `docs`, `storage:s3`, `auth:oidc`, etc. Examples: ``` feat(storage:s3): add use_path_style for MinIO compatibility fix(auth:oidc): refresh token before expiry instead of after docs(api): document /api/admin/external/:name/test build(docker): pin alpine to 3.20 to dodge ghostscript regression ``` Breaking changes: ``` feat(api)!: rename @file-explorer-share to @share-created BREAKING CHANGE: the Vue event name changed. See docs/API.md. ``` --- ## Testing ### Go ```bash cd backend go test -race -timeout 30m ./... # internal/api/handlers alone is ~9 min, past the default 10m under -race go test -race -timeout 30m -cover ./... # with coverage go test -run TestStorageS3 -v ./internal/storage/s3 ``` Where the whole suite has to be fast it runs in parts, side by side - see [Shards](#shards). For driver tests, we have integration suites under `internal/storage/*/integration_test.go` guarded by `//go:build integration`. Run with: ```bash go test -tags=integration ./internal/storage/s3 \ -test-bucket="$TEST_BUCKET" -test-region=us-east-1 ``` ### Web ```bash pnpm run test # all workspaces pnpm --filter='@brftech/filex-core' test ``` Vitest with happy-dom. **The admin app's main chunk has a size budget.** It carries the explorer from `packages/core` and must fit workbox's 2 MiB precache limit (`web/pwa.config.ts`), or `vite build` fails, so a branch that adds to core runs `pnpm -C web build && pnpm -C web size` before it is merged (it prints the chunk's size and the room left) and moves a dialog a person opens out of the chunk (`packages/core/lazySurfaces.ts`) rather than raising the limit. **A unit test never reaches the network, and it does not tidy up after itself.** `web/tests/setup.ts` refuses every request a test did not mock (`fetch`, `XMLHttpRequest`, `WebSocket`/`EventSource` and happy-dom's own loads underneath them) and fails that test with the address, even when the page swallows the error; mock the API module (`vi.mock('@/api/…')`) or stub `fetch`, and in the rare file that truly needs the network say why with `allowNetwork('…')` from `web/tests/helpers/noNetwork.ts`. An `