Thank you for your interest in contributing to Bambuddy! This document provides guidelines and instructions for contributing.
Please read and follow our Code of Conduct to keep our community welcoming and respectful.
Every contribution starts with an issue. Before writing any code or opening a pull request:
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.
Features and user-visible behavior changes must include matching documentation updates in the docs repos:
| 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 | ❌ | ❌ |
bambuddybambuddy-wiki and/or bambuddy-websiteIf your change truly doesn't need docs (internal refactor, silent bug fix), say so in the PR description and give a one-line reason.
Clone the docs repo and run it locally to see your changes rendered with the real theme before opening the PR:
bambuddy-wiki) — pip install -r requirements.txt && mkdocs serve — live-reload on http://localhost:8000bambuddy-website) — static HTML/CSS, open the changed file directly or serve with python -m http.serverReview like you would the production site. Catch broken links, layout regressions, typos, missing images. If it looks right, open the PR.
Both docs repos can be edited directly in the browser, no git clone required:
. (period) on any repo page to open VS Code in your browser, with multi-file editing and syntax highlightingClone your fork locally:
git clone https://github.com/YOUR_USERNAME/bambuddy.git
cd bambuddy
Add the upstream remote:
git remote add upstream https://github.com/maziggy/bambuddy.git
# 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
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.
# 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
Create a branch from dev for your changes:
git checkout dev
git pull upstream dev
git checkout -b feature/your-feature-name
# or
git checkout -b fix/your-bug-fix
Make your changes following our code style guidelines
Test your changes thoroughly
Commit your changes with clear, descriptive messages:
git commit -m "Add feature: description of what you added"
feature/ - New featuresfix/ - Bug fixesdocs/ - Documentation changesrefactor/ - Code refactoringtest/ - Test additions or fixesWe use Ruff for linting and formatting. Configuration is in pyproject.toml.
# 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 (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:
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
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.requirements.txt? Regenerate requirements.lock (see 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).if/try/with) or reordering functions that hold baselined errors can bring them back as new ones. Fix them while you are there.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 (pip install uv):
# 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.
We use ESLint for linting, TypeScript for type checking and knip for unused files and dependencies:
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 run automatically on git commit and include Ruff linting/formatting, trailing whitespace fixes, YAML/JSON validation, and import shadowing checks. To run manually:
pre-commit run --all-files
The frontend uses react-i18next for all user-facing text. Never hardcode user-visible strings — always use translation keys.
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:
cd frontend
npm run check:i18n
Use the useTranslation hook in your component:
import { useTranslation } from 'react-i18next';
function MyComponent() {
const { t } = useTranslation();
return <span>{t('section.myNewKey')}</span>;
}
Keys are organized by feature (e.g., spoolman., nav., common.)
ennpm run test:run before pushing — it chains the parity check, which CI runs too. Plain npm test is vitest in watch mode and skips itBambuddy has an optional authentication system. When auth is enabled, API endpoints are protected by granular permissions.
Authentication is opt-in — when disabled, all endpoints are open. The system uses RequirePermissionIfAuthEnabled which:
Use the RequirePermissionIfAuthEnabled dependency in your route:
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),
):
...
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.
Permission enum in backend/app/core/permissions.pyPERMISSION_CATEGORIESAdministrators gets all, Operators and Viewers as appropriate)RequirePermissionIfAuthEnabled| 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 |
The easiest way to run tests is with the provided scripts in the project root:
./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.
Backend — tests are in backend/tests/ with unit/ and integration/ subdirectories:
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.
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 and are in frontend/src/__tests__/:
cd frontend
npm run test:run # Single run
npm test # Watch mode
npm run test:coverage # With coverage
Pull requests trigger automated CI checks via GitHub Actions (.github/workflows/ci.yml):
All checks must pass before merging. Run ./test_all.sh locally before pushing to catch issues early.
Push your branch to your fork:
git push origin feature/your-feature-name
Create a Pull Request on GitHub:
dev branch as the base branch (not main)Wait for review - maintainers will review your PR and may request changes
Use the Bug Report template and include:
Use the Feature Request template and include:
Thank you for contributing to Bambuddy!