# Contributing to Bambuddy Thank you for your interest in contributing to Bambuddy! This document provides guidelines and instructions for contributing. ## Table of Contents - [Code of Conduct](#code-of-conduct) - [Before You Start](#before-you-start) - [Documentation Requirements](#documentation-requirements) - [Getting Started](#getting-started) - [Development Setup](#development-setup) - [Making Changes](#making-changes) - [Code Style](#code-style) - [Internationalization (i18n)](#internationalization-i18n) - [Authentication & Permissions](#authentication--permissions) - [Testing](#testing) - [CI Pipeline](#ci-pipeline) - [Submitting Changes](#submitting-changes) - [Reporting Bugs](#reporting-bugs) - [Requesting Features](#requesting-features) ## Code of Conduct Please read and follow our [Code of Conduct](CODE_OF_CONDUCT.md) to keep our community welcoming and respectful. ## Before You Start **Every contribution starts with an issue.** Before writing any code or opening a pull request: 1. **Open a new issue** or **comment on an existing one** describing what you'd like to work on 2. **Wait for agreement** — discuss the approach with a maintainer so we're aligned on scope and direction 3. **Get assigned** — once we agree, a maintainer will assign the issue to you 4. **Then start coding** — only open a PR for an issue that is assigned to you **No assigned issue = no PR.** Pull requests without a corresponding assigned issue will be closed. This keeps everyone on the same page, avoids wasted effort on changes that may not fit the project's direction, and prevents multiple contributors from working on the same thing. ## Documentation Requirements Features and user-visible behavior changes **must** include matching documentation updates in the docs repos: - **[bambuddy-wiki](https://github.com/maziggy/bambuddy-wiki)** — end-user guide (installation, configuration, feature walkthroughs, reference) - **[bambuddy-website](https://github.com/maziggy/bambuddy-website)** — marketing site (updated only when the change affects public claims or feature lists) ### When docs updates are required | Change | Needs wiki? | Needs website? | |---|---|---| | New feature | ✅ | Maybe (if in the feature list) | | New config key / setting | ✅ | ❌ | | New port, URL, API endpoint | ✅ | ❌ | | Installation or upgrade steps change | ✅ | ✅ | | UI change that affects screenshots | ✅ | ❌ | | Bug fix with no observable behavior change | ❌ | ❌ | | Internal refactor | ❌ | ❌ | | Test-only change | ❌ | ❌ | ### Workflow 1. Open your code PR here in `bambuddy` 2. Open companion PR(s) in `bambuddy-wiki` and/or `bambuddy-website` 3. **Link the companion PR(s) in the code PR description** (the PR template has a dedicated section) 4. Merge the PRs together — usually code first, then docs, unless the docs reference new things that don't exist yet If your change truly doesn't need docs (internal refactor, silent bug fix), say so in the PR description and give a one-line reason. ### Previews before you merge Clone the docs repo and run it locally to see your changes rendered with the real theme before opening the PR: - **Wiki** (`bambuddy-wiki`) — `pip install -r requirements.txt && mkdocs serve` — live-reload on `http://localhost:8000` - **Website** (`bambuddy-website`) — static HTML/CSS, open the changed file directly or serve with `python -m http.server` Review like you would the production site. Catch broken links, layout regressions, typos, missing images. If it looks right, open the PR. ### Editing docs without a local clone Both docs repos can be edited directly in the browser, no `git clone` required: - **GitHub web editor** — click the pencil icon on any file in the repo - **github.dev** — press `.` (period) on any repo page to open VS Code in your browser, with multi-file editing and syntax highlighting ## Getting Started 1. **Fork the repository** on GitHub 2. **Clone your fork** locally: ```bash git clone https://github.com/YOUR_USERNAME/bambuddy.git cd bambuddy ``` 3. **Add the upstream remote**: ```bash git remote add upstream https://github.com/maziggy/bambuddy.git ``` ## Development Setup ### Prerequisites - Python 3.11+ - Node.js 20+ - npm ### Backend Setup ```bash # Create virtual environment python3 -m venv venv source venv/bin/activate # On Windows: venv\Scripts\activate # Install dependencies: the pinned versions that ship (see "Dependencies" below) pip install -r requirements.lock pip install -r requirements-dev.txt # Dev/test dependencies (pytest, ruff, bandit, etc.) # Install pre-commit hooks pip install pre-commit pre-commit install # Run backend (--loop asyncio matches production; avoids a uvloop TLS bug # that can truncate Virtual Printer FTP uploads on slow storage — see #1896) DEBUG=true uvicorn backend.app.main:app --reload --host 0.0.0.0 --port 8000 --loop asyncio ``` ### Frontend Setup ```bash cd frontend # Install dependencies npm install # Run development server npm run dev ``` The frontend will be available at `http://localhost:5173` and will proxy API requests to the backend. ### Running with Docker ```bash # Run the full application docker compose up -d --build # Run tests in Docker (mirrors CI) docker compose -f docker-compose.test.yml run --rm backend-test docker compose -f docker-compose.test.yml run --rm frontend-test ``` ## Making Changes 1. **Create a branch** from `dev` for your changes: ```bash git checkout dev git pull upstream dev git checkout -b feature/your-feature-name # or git checkout -b fix/your-bug-fix ``` 2. **Make your changes** following our code style guidelines 3. **Test your changes** thoroughly 4. **Commit your changes** with clear, descriptive messages: ```bash git commit -m "Add feature: description of what you added" ``` ### Branch Naming - `feature/` - New features - `fix/` - Bug fixes - `docs/` - Documentation changes - `refactor/` - Code refactoring - `test/` - Test additions or fixes ## Code Style ### Backend (Python) We use [Ruff](https://github.com/astral-sh/ruff) for linting and formatting. Configuration is in `pyproject.toml`. ```bash # Check linting ruff check backend/ # Auto-fix issues ruff check --fix backend/ # Format code ruff format backend/ # Check formatting without changes ruff format --check backend/ ``` Types are checked with [basedpyright](https://docs.basedpyright.com/) (configuration in `pyproject.toml`). Errors that existed when the check was introduced are listed in `.basedpyright/baseline.json` and pass; a new one fails the check. The check runs against the pinned library versions in `requirements-typecheck.lock`, because other versions report different errors. It needs Python 3.11 or newer on Linux, macOS on Apple Silicon, or WSL (the lock is built for Linux; on Intel Macs some pinned packages have no wheels). `./test_backend.sh` runs it for you: it creates `.typecheck-venv` on first use when Python 3.11 is available (through `uv`, or as `python3.11`), and rebuilds it whenever the lock changes. Running the commands by hand, re-run the `pip install` after a lock change: ```bash python3 -m venv .typecheck-venv # Python 3.11 or newer .typecheck-venv/bin/pip install -r requirements-typecheck.lock .typecheck-venv/bin/basedpyright --pythonpath .typecheck-venv/bin/python ``` - **Fixed an error from the baseline?** A local run without new errors removes it from `baseline.json`; commit that change with your fix. (With a new error in the same run, the baseline is left as it is until that error is fixed.) CI never rewrites the baseline and fails if it still lists an error that is gone. - **Added or changed a dependency in `requirements.txt`?** Regenerate `requirements.lock` (see [Dependencies](#dependencies)) and then the type-check lock, with the command in `requirements-typecheck.txt`, in the same PR. If that moves the pin of a package already in the lock and the check then reports errors in code you did not touch, regenerate the baseline in the same commit (see `requirements-typecheck.txt`). - **Moved or edited code that has a baselined error?** The baseline matches each file's errors in order by rule and column span, not by line number. Editing such a line, re-indenting it (wrapping it in `if`/`try`/`with`) or reordering functions that hold baselined errors can bring them back as new ones. Fix them while you are there. ### Dependencies `requirements.txt` lists what Bambuddy needs, with lower bounds. `requirements.lock` pins the exact versions, for every supported Python (3.11+) and platform, and it is what Docker, the installers, the update scripts and CI install: everyone runs the versions that were tested. Both files change together, and a test (`test_requirements_lock.py`) fails if the lock doesn't cover `requirements.txt`. Regenerating needs [uv](https://docs.astral.sh/uv/) (`pip install uv`): ```bash # After adding or changing a dependency (keeps every other pin) uv pip compile requirements.txt --universal --python-version 3.11 -o requirements.lock # Moving every package to its newest version, on purpose (e.g. before a release) uv pip compile requirements.txt --universal --python-version 3.11 --upgrade -o requirements.lock ``` Then regenerate the type-check lock (command in `requirements-typecheck.txt`), install the new lock into your venv, and run the full test suite before committing. ### Frontend (TypeScript/React) We use ESLint for linting, TypeScript for type checking and knip for unused files and dependencies: ```bash cd frontend # Lint npm run lint # Type check npm run typecheck # Unused files and dependencies npm run check:knip ``` knip follows imports from the entry points (`index.html`, the Vite and Vitest configs, and `public/sw.js`, which the browser loads by URL). A file nothing imports, or a package nothing uses, fails the check: delete it, or add it to `frontend/knip.jsonc` with a comment saying why if something loads it in a way knip can't see. ### Pre-commit Hooks Pre-commit hooks run automatically on `git commit` and include Ruff linting/formatting, trailing whitespace fixes, YAML/JSON validation, and import shadowing checks. To run manually: ```bash pre-commit run --all-files ``` ## Internationalization (i18n) The frontend uses [react-i18next](https://react.i18next.com/) for all user-facing text. **Never hardcode user-visible strings** — always use translation keys. ### Locale Files Translations live in `frontend/src/i18n/locales/`. `en.ts` is the reference locale; every other `*.ts` file in that directory is checked against it. The parity check discovers the directory at runtime, so a new locale is picked up automatically — this file never needs updating when one is added. To see the current set of locales and check your work: ```bash cd frontend npm run check:i18n ``` ### Adding New Strings 1. Add the key to the appropriate section in **every** locale file 2. Use the `useTranslation` hook in your component: ```tsx import { useTranslation } from 'react-i18next'; function MyComponent() { const { t } = useTranslation(); return {t('section.myNewKey')}; } ``` 3. Keys are organized by feature (e.g., `spoolman.`, `nav.`, `common.`) ### Important Notes - Every locale file must use the **same key structure** — same nesting, same key paths - Always add keys to **every** locale to maintain parity, with real translations rather than English placeholders — the check flags leaves that are identical to `en` - Run `npm run test:run` before pushing — it chains the parity check, which CI runs too. Plain `npm test` is vitest in watch mode and skips it - If you find structural inconsistencies between locales, fix them — different key paths cause silent fallback to English ## Authentication & Permissions Bambuddy has an optional authentication system. When auth is enabled, API endpoints are protected by granular permissions. ### How It Works Authentication is **opt-in** — when disabled, all endpoints are open. The system uses `RequirePermissionIfAuthEnabled` which: - Checks if auth is enabled in settings - If disabled: allows the request through (no-op) - If enabled: validates JWT token/API key and checks the user has the required permission ### Adding Auth to New Endpoints Use the `RequirePermissionIfAuthEnabled` dependency in your route: ```python from backend.app.core.auth import RequirePermissionIfAuthEnabled from backend.app.core.permissions import Permission @router.get("/my-resource") async def get_my_resource( _: User | None = RequirePermissionIfAuthEnabled(Permission.RESOURCE_READ), ): ... ``` ### Permission Convention Permissions follow the `resource:action` pattern (e.g., `filaments:read`, `printers:control`). Standard actions: | Action | Usage | |--------|-------| | `read` | View/list resources | | `create` | Create new resources | | `update` | Modify existing resources | | `delete` | Remove resources | Some resources have additional actions. Examples: `printers:control` for live printer controls such as stop/pause/resume, `printers:files` for printer storage access, `queue:create` for creating queue items that may dispatch immediately when scheduled ASAP, `library:upload` for File Manager uploads/imports, and `archives:reprint_own` / `archives:reprint_all` for archive reprint eligibility. Archive reprint still needs `queue:create` before it can enqueue a job. ### Adding New Permissions 1. Add the permission to the `Permission` enum in `backend/app/core/permissions.py` 2. Add it to the appropriate category in `PERMISSION_CATEGORIES` 3. Add it to the relevant default groups (`Administrators` gets all, `Operators` and `Viewers` as appropriate) 4. Use it in your route with `RequirePermissionIfAuthEnabled` ### Default Groups | Group | Access Level | |-------|-------------| | **Administrators** | All permissions | | **Operators** | Full control of printers, own items in archives/queue, read-only settings | | **Viewers** | Read-only access to all resources | ## Testing The easiest way to run tests is with the provided scripts in the project root: ```bash ./test_frontend.sh # TypeScript check + ESLint + knip + Vitest ./test_backend.sh # Ruff lint/format + type check (if .typecheck-venv exists) + PostgreSQL upgrade check (if Docker is available) + pytest (parallel) ./test_docker.sh # Full Docker build, unit tests, and integration tests ./test_all.sh # All of the above (frontend → backend → docker) ./test_security.sh # Security scans (bandit, pip-audit, npm-audit) ``` `test_docker.sh` supports flags like `--backend-only`, `--skip-integration`, `--fresh` — run with `--help` for details. `test_security.sh` runs fast scans by default. Use `--full` for the complete suite (CodeQL, Trivy, etc.) or specify individual scans like `./test_security.sh bandit codeql`. ### Running Tests Individually **Backend** — tests are in `backend/tests/` with `unit/` and `integration/` subdirectories: ```bash pytest backend/tests/ -v # All tests pytest backend/tests/unit/ # Unit tests only pytest backend/tests/ --cov=backend # With coverage ``` `conftest.py` redirects `DATABASE_URL` to a throwaway SQLite file before any app module is imported, and aborts the run if that redirect did not take. Your `.env` is ignored for the duration, so the suite cannot reach the database you develop against — it exercises code paths that open their own sessions, and some of those write. Don't undo that override to "test against real data": point `DATABASE_URL` at a copy instead. ### Database migrations (PostgreSQL) The test suite runs on SQLite only. `scripts/check_postgres_upgrade.py` covers PostgreSQL: it builds a database with older releases' code (the first release with PostgreSQL support and the newest stable one), upgrades it with the current code twice, and compares its schema with a fresh install. `test_backend.sh` runs it in a throwaway `postgres:16-alpine` container when Docker is available and the clone has the release tags (`git fetch --tags`); CI runs it too. A fresh install gets every table and column straight from the models, while an existing database only gets what the migrations in `backend/app/core/database.py` add. So a new column, constraint or index needs a migration that builds it exactly as the model does, including nullability and default; the check fails when an upgraded database ends up different. A migration that only fails with data in the database, such as a constraint that existing rows violate, gets a scenario in `SCENARIOS` in the same script: a fixed chain of old releases to build the database, rows to add, and a query that must find nothing wrong after the upgrade. `scripts/postgres_schema_drift.txt` can list known drift, each line an object and exactly how it differs; a listed object that starts to differ in another way fails like new drift, and so does a line that no longer drifts. The drift found when the check was introduced has been fixed and the list is empty: fix new drift with a migration rather than listing it. **Frontend** — tests use [Vitest](https://vitest.dev/) and are in `frontend/src/__tests__/`: ```bash cd frontend npm run test:run # Single run npm test # Watch mode npm run test:coverage # With coverage ``` ## CI Pipeline Pull requests trigger automated CI checks via GitHub Actions (`.github/workflows/ci.yml`): - **Backend**: Ruff lint + format check, basedpyright type check (against the baseline), unit/integration tests, PostgreSQL upgrade check, pip-audit - **Frontend**: ESLint, knip (unused files and dependencies), TypeScript type check, Vitest tests, production build - **Docker**: Full image build, backend/frontend tests in Docker, integration health checks - **Security**: CodeQL analysis, dependency audits All checks must pass before merging. Run `./test_all.sh` locally before pushing to catch issues early. ## Submitting Changes 1. **Push your branch** to your fork: ```bash git push origin feature/your-feature-name ``` 2. **Create a Pull Request** on GitHub: - **Always target the `dev` branch** as the base branch (not `main`) - Use a clear, descriptive title - Fill out the PR template completely - Link any related issues - Include before/after screenshots for any visual changes 3. **Wait for review** - maintainers will review your PR and may request changes ### PR Guidelines - Keep PRs focused and reasonably sized - One feature or fix per PR - Update documentation if needed - Add tests for new functionality - Ensure all tests pass - Follow the existing code style - **Visual changes require screenshots** — if your PR changes any frontend UI, include before/after screenshots showing the old and new appearance ## Reporting Bugs Use the [Bug Report template](https://github.com/maziggy/bambuddy/issues/new?template=bug_report.yml) and include: - Clear description of the bug - Steps to reproduce - Expected vs actual behavior - Your environment (OS, Python version, browser) - Printer model and firmware version - Relevant logs ## Requesting Features Use the [Feature Request template](https://github.com/maziggy/bambuddy/issues/new?template=feature_request.yml) and include: - Clear description of the feature - Use case / problem it solves - Proposed solution - Alternatives considered ## Questions? - Check the [Documentation](http://wiki.bambuddy.cool) - Open a [Discussion](https://github.com/maziggy/bambuddy/discussions) - Review existing [Issues](https://github.com/maziggy/bambuddy/issues) --- Thank you for contributing to Bambuddy!