First off, thank you for considering contributing to LibreDB Studio! It's people like you that make LibreDB Studio such a great tool.
This project and everyone participating in it is governed by our Code of Conduct. By participating, you are expected to uphold this code.
Open an issue or a pull request in Chinese (中文) or Japanese (日本語) if that is easier for you. You do not need fluent English to report a bug or propose a change, and a report we have to translate is far better than one you did not send. Maintainers will usually reply in English; say so if that does not work for you.
This applies to the conversation, not to the repository. Everything that lands in the tree stays in English: code, comments, commit messages, documentation and the pull request title. The exceptions are the translated READMEs themselves (README_es.md, README_hi.md, README_ja.md, README_pt.md, README_ru.md, README_ur.md, README_zh.md), which are maintained in their own language.
If you are updating a translated README, note that bun run readme:check enforces that its engine table and install commands match README.md. Translations may cover fewer install channels, but a command must never be paraphrased - a reader copy-pastes it.
Each translated README also carries a translation-lag banner: a blockquote above its first heading, in that file's own language, linking to README.md as the version that wins when the two disagree. readme:check enforces its presence, because the guard's other invariants cover the engine table, the install commands and one warning only - a translation can be stale in ways nothing in CI will report.
Before creating bug reports, please check the existing issues to avoid duplicates. When you create a bug report, include as many details as possible:
- Use a clear and descriptive title
- Describe the exact steps to reproduce the problem
- Describe the behavior you observed and what you expected
- Include screenshots if possible
- Include your environment details (OS, browser, Node.js version)
Feature suggestions are welcome! Please provide:
- A clear and descriptive title
- A detailed description of the proposed feature
- Explain why this feature would be useful
- Include mockups or examples if applicable
-
Start from an issue. Open one or comment on an existing one before you write code, and reference it from the PR (
Closes #123). A PR with no linked issue is hard to review and, during Hacktoberfest, does not count. -
Fork the repository and create your branch from
main. -
Write the failing test first, then the fix. Every change that adds or alters executable lines lands with its tests in the same PR. The CI gate is 100% line coverage (
scripts/check-coverage.mjsfails the requiredUnit & Integration Testsjob below it), so a PR without tests cannot merge no matter how small the change. -
Run the checks locally when your environment supports them. They mirror the required CI checks:
(cd vscode-extension && npm ci --ignore-scripts) # once, and again when its package-lock.json changes bun run format && bun run lint && bun run typecheck && bun run knip \ && bun run readme:check && bun run chart:check && bun run channels:showcase:check \ && bun run security:check && bun run test && bun run build
CI is the merge gate. If you cannot run a command locally, list that command and the reason under a
Testingheading in your PR body; submit the PR, and a maintainer will approve the fork's workflow run so CI can verify it. You do not need to withdraw correct work because a local tool is unavailable.If your sandbox can reach the npm registry,
npm install -g bunis another way to install Bun. Helm is only needed to run the chart tests: without itbun run testleaves those files out and names them, and CI runs them. The devcontainer setup below provides both tools.Always
bun run test, never barebun testover a directory: the runner gives each test file its own bun process, andbun test tests/apiputs them all in one, where one file'smock.module()becomes every file's. To run a single file, name it:bun tests/run-tests.ts tests/unit/x.test.ts.bun run test:coverage && bun run coverage:checkprints the exact uncoveredfile:lineranges.bun tests/run-tests.ts --jobs=Nlowers the concurrency, which by default is one job per available CPU: that follows CPU affinity and a cgroup CPU limit, but no memory limit, and a job peaks at roughly 60 to 340 MiB. Use it in a memory-limited container that has no CPU limit, and when a file comes back as killed by SIGKILL from outside the runner, which on Linux is usually the OOM killer. A flag meant forbun testgoes past a--the runner can see, which means invoking the runner directly with a selector first:bun tests/run-tests.ts tests/unit -- --bail.bun run test -- --baildoes not work, becausebun runremoves the first--before the script sees it, and bun removes one that sits straight after the script path too. -
Keep the provider triad in lockstep. Anything under
src/lib/db/providers/**has a matchingdocs/providers/<type-id>.mdandtests/integration/db/<type-id>-provider.test.ts; a change to one moves the other two in the same PR. -
Localized READMEs are guarded.
README_es.md,README_hi.md,README_ja.md,README_pt.md,README_ru.md,README_ur.md, andREADME_zh.mdmust list the same engines asREADME.mdand quote install commands verbatim;bun run readme:checkenforces it. -
Follow the coding style, write clear commit messages, and update documentation with the code.
Set up the upstream remote once after cloning your fork:
git remote add upstream https://github.com/libredb/libredb-studio.git
git remote -vRefresh your feature branch before asking for review:
git status # commit or stash first; rebase refuses to run dirty
git fetch upstream # in a direct clone: git fetch origin
git rebase upstream/main # in a direct clone: git rebase origin/main
git push --force-with-lease # never plain --forceRebase before review for three reasons that are specific to this repository.
Required status checks are not strict, so a green run can be against a main that has already moved and GitHub will not force the branch to update.
The Security Scanning secret check scans the commits your branch adds, so deleting a credential in a later commit does not clear the finding; rewrite the commit that introduced it instead.
A stale branch that edits a shared file can merge cleanly while restoring an older version of someone else's work, and rebasing surfaces that drift before review.
--force-with-lease refuses the push when the remote branch has moved since your last fetch, which is the case where plain --force could delete someone else's commit.
If rebase reports conflicts, resolve each file, then continue:
git add <file>
git rebase --continueUse git rebase --abort to return to where you started.
Do not rebase after a maintainer has started reviewing unless they ask you to. Force-pushing replaces commit SHAs and loses the review's line anchors, so prefer new commits during review and rebase once at the end when asked.
After a rebase, run the complete local gate from the Run the checks locally step again. The whole branch is re-verified from scratch, so do not rely on results from before the history rewrite.
The issues we have vetted for outside pickup are labeled
good first issue
(small) and
help wanted
(medium). Each states what is wrong, where to look and what "done" looks like, and none needs a live
database cluster or a cloud account. That list is the one to pick from during Hacktoberfest, Social
Winter of Code, GSSoC or any other contributor program. Entries in docs/BACKLOG.md that carry no
label have not been vetted for outside pickup; ask in an issue first if one interests you.
- Claim before you start. Comment on the issue so two people do not build the same fix. A claimed issue with no activity for two weeks is open again.
- A claim is a courtesy, not a lock. It cannot reach somebody who already had the issue open, because GitHub sends no notification for a comment on a page you are already reading. So two people do occasionally arrive at the same issue, and when that happens neither of them did anything wrong. We decide by the clock rather than by the claim: work that was already in flight when the claim was posted is not queue-jumping, and a pull request that is already delivered is reviewed on its merits. Nobody is asked to write the same change twice, so whoever does not land it is offered the nearest open issue instead, and a review on the other pull request is credited here the same as code.
- What counts. A PR that references its issue, includes tests and passes the gate above. During
October we also add
hacktoberfest-acceptedto merged PRs from the labeled list, for participants whose program still looks for it. - What does not count. PRs that only reformat, rename, fix a typo without an issue, add a
trailing comment or bump a version are closed with the
spamorinvalidlabel. Machine-generated PRs that do not run the tests fall in the same bin. - Maintainers: the block pasted at the foot of a curated issue lives in
.github/curated-issue-footer.md. Copy it from there rather than retyping it;tests/unit/curated-issue-footer.test.tsholds its CI-gate paragraph to the copy in step 4 above.
The repository keeps the hacktoberfest topic for discoverability. Note that Hacktoberfest 2026
itself is organised around in-person and online events and no longer counts pull requests; the
labels above are how this repository welcomes contributors in any month.
Every change here lands with its tests in the same pull request, under a hard 100% line-coverage
gate and every check main requires. Clearing that bar says something about you, so we write down who
cleared it and what it earns.
There is no threshold on this page, and that is deliberate. We do not count merged pull requests, changed lines, closed issues or anything else. A count measures how often somebody showed up; it cannot see the care they took, the bug nobody else found, or the question they answered for a stranger at midnight. Five small changes and one careful one are not the same thing in either direction, and a number cannot tell you which is which. So these rungs are judgements, made by the maintainers and written down where you can read them and disagree.
- Contributor — you landed a change. You are listed in
CONTRIBUTORS.mdwith a link to it, so the entry is evidence rather than a thank-you. Use it wherever you need to show what you have shipped. - Trusted contributor — someone we would hand an issue to without a conversation first. In practice: assign yourself any open labeled issue without asking, and your review on someone else's pull request is read as a review rather than a comment. Nothing to apply for and nothing to reach; we put you here, and the reason is written beside your name.
- Area owner — you own one area: a database provider, or a subsystem such as the Helm chart or
the SQL editor. For a provider that means the triad the repository is built on — the code under
src/lib/db/providers/, the doc underdocs/providers/and the tests undertests/integration/db/move together, and a change to your area is reviewed with you. Offered by the maintainers, and offered to people who have already been answering questions about that area. - Maintainer — carries the project: the releases, the review, and the decisions nobody else can make. Not a rung you climb to from the ones above it, which is why it is listed separately rather than at the top of the same ladder.
Maintainers are on CONTRIBUTORS.md alongside everyone else. Keeping their work
off the page would make it read as a guest list rather than a record, and the standard the page holds
people to is one they are held to as well. Bots and coding agents are not listed: dependabot,
Copilot and claude all appear in the commit history, none of them is a person, and putting them
beside people would blur the only thing the page is for.
Falling off a rung is not a thing. If you stop contributing, you keep what you earned.
Maintainers: adding the contributor to CONTRIBUTORS.md is part of merging an external pull
request, not a later sweep. tests/unit/contributors-doc.test.ts checks the page's shape and the
ladder's two halves against each other, but it deliberately does not check that the list is
complete — completeness cannot be measured in a shallow CI clone, so it stays a human step that is
honest about being one.
Open the repository in GitHub Codespaces, or use Dev Containers: Reopen in Container in VS Code
with Docker running. .devcontainer/devcontainer.json provides
Node.js 24, Bun 1.4.4 and Helm 4.1.3, matching the required Node version and the Bun/Helm versions
used by CI, plus 7-Zip for the packaging tests. The first creation installs the locked JavaScript
dependencies and builds the chart's PostgreSQL dependency, so it needs access to the npm registry
and the chart registries.
JavaScript dependencies live in a container volume, keeping Linux native modules separate from any dependencies installed on your host and avoiding slow shared-filesystem installs on Docker Desktop.
Once setup finishes, run the checks from step 4 in the container terminal. Start the app with
bun run dev; the container forwards port 3000. Database containers are optional and are not
started by this setup.
The suite runs on Linux, macOS and Windows, from whichever shell the platform gives you.
bun run test is bun tests/run-tests.ts, a TypeScript runner rather than a shell script, and CI runs it on ubuntu-latest, macos-latest and windows-latest.
| Tool | Version | Needed for |
|---|---|---|
| Bun | 1.4.4, the packageManager pin |
Installing, the dev server, the build, and the test runner itself |
| Node.js | 24+, the engines floor |
The scripts/*.mjs gates, including merge-lcov.mjs and check-coverage.mjs |
| Git | any | Cloning, and on Windows it is also where the POSIX tools below come from |
| Helm | 4.1.3, the version CI runs | Optional locally: the chart tests, see below |
A POSIX shell plus tar, zip, unzip and 7z |
any | The packaging tests, which run the packaging/ shell scripts and unpack what they produce |
Twelve of the thirteen helm-chart-*.test.ts files under tests/unit/ spawn the helm binary; the exception is helm-chart-readme-recipes.test.ts, a static lint over the chart README.
Each of the twelve opens with // @requires helm, and the runner reads that before it starts a file.
Where helm is not on PATH, or the chart's PostgreSQL subchart is not built, bun run test does not start those files: it runs the rest, and its summary lists them under "Files not run on this machine" with the reason and the command that fixes it.
Selecting only chart tests on such a machine is an error rather than an empty green run.
CI sets LIBREDB_REQUIRE_HELM=1, which makes the same condition stop the run before anything starts, so the chart tests are never left out of a gate.
If you change the chart, install Helm and run them before you push.
A new test file that runs helm needs the marker too, and tests/unit/test-runner-requirements.test.ts fails until it has it.
To run the chart tests, install Helm; the PostgreSQL subchart tarball is gitignored (*.tgz), so a fresh clone also needs:
helm repo add bitnami https://charts.bitnami.com/bitnami
helm dependency build charts/libredb-studio --skip-refreshTrap: a stale docker login can make that build fail with 401 Unauthorized from registry-1.docker.io even though the chart is anonymously pullable. docker logout fixes it.
A tool your platform does not have is not a failure here.
The tests that need it become skips whose titles carry the reason, the summary lists every one of them under its file at the end of the run, and bun run test still ends green.
So the table above is what you need to run the WHOLE suite; a machine without one of those tools runs the rest of it.
On Windows the POSIX tools come from the Git for Windows installation the clone already needed, and the tests locate them through git itself rather than through PATH.
PowerShell's PATH carries git.exe but not the bin and usr\bin directories beside it that hold bash.exe, grep.exe and unzip.exe, and where WSL is installed a bare bash does resolve, to C:\Windows\System32\bash.exe, a Linux shell that cannot read the Windows temp paths the fixtures hand it.
So tests/helpers/posix-tools.ts asks the git binary for its exec path, derives the installation root from it, falls back to %LOCALAPPDATA%\Programs\Git and the two Program Files defaults, and spawns each tool by absolute path; 7-Zip is looked for at C:\Program Files\7-Zip\7z.exe as well as on PATH.
Git for Windows carries no zip, so the tests that build the Azure package skip there whatever else you install (docs/BACKLOG.md D87).
Assertions about POSIX file modes skip on Windows in every case: NTFS has no exec bit, and Windows cannot exec an extension-less #! script.
macOS needs nothing of its own, and the one thing that used to stop it is gone: the old shell runner called mapfile, a bash 4 builtin that the system bash 3.2 does not have, and the runner is TypeScript now.
The gap that remains is 7z, which the tests look for by that name, so an installation that provides only 7zz leaves the standalone-zip packaging tests skipped.
bash, tar, zip and unzip come with the system, so everything else runs.
You do not need a .env file or a data/ directory to run the tests.
tests/setup.ts, which bunfig.toml preloads into every test process, pins the credentials and settings the suite runs under, so a local .env cannot decide a test's outcome.
# Clone your fork
git clone https://github.com/YOUR_USERNAME/libredb-studio.git
cd libredb-studio
# Install dependencies
bun install
# Install the VS Code extension's packages, which bun run knip reads
(cd vscode-extension && npm ci --ignore-scripts)
# Copy environment example
cp .env.example .env.local
# Start development server
bun devNone are required: bun dev starts with an empty .env.local and the app's
zero-config first run generates the admin credentials and the JWT secret, printing
the password once to the dev-server output. Set them to pin known values instead
(USER_PASSWORD additionally creates the optional non-admin account, which is
never generated):
ADMIN_PASSWORD=
USER_PASSWORD=
JWT_SECRET=Optional (for AI features):
LLM_PROVIDER=gemini
LLM_API_KEY=your_api_key
LLM_MODEL=gemini-2.5-flashAny variable you add to .env.example whose name ends in _PATH, _DIR or _FILE must also
be classified in bin/lib/launcher-utils.mjs, in one of two
lists, or the required Unit & Integration Tests check fails:
PATH_VARIABLES— the value is a filesystem path, and thenpx @libredb/studiolauncher resolves a relative value against the directory it was started from.URL_PATH_VARIABLES— the name looks like a path but the value is not a file or directory (a URL prefix or a mount name); the entry carries a one-line reason and the launcher leaves the value alone.
A guard test in tests/unit/launcher-utils.test.ts
enforces this, so classify the variable when you add it rather than waiting for CI to flag it.
We provide a ready-to-use PostgreSQL setup with sample data for testing:
# Start PostgreSQL with sample e-commerce data
docker compose -f docker/postgres.yml up -d
# Connect with:
# Host: localhost, Port: 5432, Database: libredb_dev
# User: postgres, Password: postgresIncludes:
- PostgreSQL 17 with
pg_stat_statementsenabled - E-commerce sample schema (customers, products, orders)
- 100+ records across multiple tables
- Pre-built views for reporting
This is especially useful for testing the Monitoring Dashboard features.
src/
├── app/ # Next.js App Router
│ ├── api/ # API routes
│ ├── admin/ # Admin pages
│ └── login/ # Login page
├── components/ # React components
├── hooks/ # Custom React hooks
└── lib/
├── db/ # Database providers (Strategy Pattern)
├── llm/ # LLM providers (Strategy Pattern)
└── ... # Utilities
bun dev # development server (Turbopack)
bun run build # production build
bun start # production server
bun run format # Biome formatter check (format:fix to write)
bun run lint # oxlint, then ESLint 9
bun run typecheck # TypeScript strict
bun run knip # unused files, exports and dependencies (run npm ci in vscode-extension/ first)
bun run test # every test file, one bun process each; never bare `bun test`. Without Helm the chart tests are listed as not run, see Prerequisites
bun run test:unit # one layer; also test:api, test:integration, test:hooks, test:security, test:evals, test:components
bun run test:coverage # coverage report (merged lcov)
bun run coverage:check # enforce 100% line coverage on the merged lcov
bun run readme:check # localized README drift guard
bun run chart:check # Helm chart version sync guard
bun run security:check # security posture drift guard
bun run build:lib # @libredb/studio package dist (after changing anything under src/exports/)Two checks run against every pull request. Both are reproducible locally, and reproducing them is faster than waiting for CI.
Committed secrets. This one can fail your pull request. It scans only the commits your branch adds, and the command below works from a clone and from a git worktree alike:
# In a worktree `.git` is a file that points into the main checkout's
# `.git/worktrees/`, so that directory has to exist inside the container too.
# In a clone the path is just "$PWD/.git".
GIT_COMMON_DIR="$(git rev-parse --path-format=absolute --git-common-dir)"
docker run --rm \
-v "$PWD:$PWD:ro" -v "$GIT_COMMON_DIR:$GIT_COMMON_DIR:ro" -w "$PWD" \
ghcr.io/gitleaks/gitleaks@sha256:c00b6bd0aeb3071cbcb79009cb16a60dd9e0a7c60e2be9ab65d25e6bc8abbb7f \
git --no-banner --redact --config "$PWD/.gitleaks.toml" \
--log-opts="--diff-merges=first-parent origin/main..HEAD"A scan that reports 0 commits scanned. checked nothing, and is not a pass: either git could not open the repository, so a mount above is missing, or origin/main..HEAD holds no commits.
If it reports a real credential, rotate it first — the value is already in every
clone. If it reports a fixture or placeholder, copy the finding's own
Fingerprint (commit:file:rule:startline, printed in the JSON report the
command above can produce with --report-format json) into .gitleaksignore
with a comment explaining why; that suppresses exactly this one finding, so a
real secret added later — even the same fabricated literal, in a new commit —
is still reported. .gitleaks.toml's [[allowlists]] is for the narrower case
of a whole rule being unconditionally noisy for a reviewable reason, not for a
single fixture; an allowlist that names no targetRules is rejected by
tests/unit/gitleaks-config.test.ts, because it would exempt that path from
every rule the scanner has.
Vulnerable dependencies. This one reports on pull requests and never fails them. The quickest local view needs no container:
bun auditbun audit reports every severity and does not tell you whether a fix exists, so
expect a long list; it is a starting point, not a verdict. The scan CI actually
runs covers the npm, Rust and Go ecosystems together (bun.lock,
desktop/src-tauri/Cargo.lock, the launcher's go.mod) and includes the
fixed-version column bun audit lacks:
docker run --rm -v "$PWD:/repo:ro" -w /repo \
ghcr.io/aquasecurity/trivy@sha256:7cced7cae583819fc7806d4cbc0dbbc7cad18b99f7d3e235192e6da8c091045c \
fs --scanners vuln --ignorefile /repo/.trivyignore.yaml \
--skip-dirs node_modules --skip-dirs .next --skip-dirs dist --skip-dirs coverage .Only a CRITICAL finding with an available fix gates anything, and only outside
pull requests. If you hit one, take the fix and commit bun.lock. Suppressing it
in .trivyignore.yaml is the last resort and requires a justification and an
expiry date.
Touching anything packaged under charts/libredb-studio/ pulls in two invariants
that CI enforces and that nothing in the chart itself hints at. Both are checked
by one command, and running it locally is faster than reading a CI log:
bun run chart:checkThe operator carries a verbatim copy. operator/helm-charts/libredb-studio/
is a byte-for-byte mirror of the source chart, because the OLM operator embeds
the chart rather than fetching it. Never hand-edit the copy — change the source
chart and regenerate:
bun run chart:bumpAn already-released chart version cannot be re-published. Chart releases are
immutable: re-publishing a version that already has a libredb-studio-<version>
tag would mutate the released index entry and the OCI digest that existing users
resolve (#167). So when the current version: in Chart.yaml is already tagged,
raise it by hand — both version: in Chart.yaml and the --version example in
charts/libredb-studio/README.md.
chart:bump deliberately will not raise version: for you while appVersion
is already in sync with package.json, so this step is easy to miss; chart:check
is what catches it. appVersion tracks the app's package.json version and is
the one field chart:bump does maintain.
Finally, the chart should lint clean:
helm dependency build charts/libredb-studio
helm lint charts/libredb-studio --strictCI's test and lint lanes run Helm 4.1.3, so that is the client to develop against.
That is not the chart's floor: the chart README states Helm >= 3.12, and CI's only
Helm 3 evidence is 3.16.0. The release workflow's ct install job stays on Helm 3.16
deliberately, so a Helm 3 client keeps installing the chart source - see
tests/unit/helm-pin-matrix.test.ts, and R1 in docs/BACKLOG.md for what that
still does not cover.
- Use TypeScript for all new code
- Define proper types/interfaces
- Avoid
anytype when possible
- Use functional components with hooks
- Follow the existing component patterns
- Keep components focused and small
- Use Tailwind CSS for styling
- Follow the existing design patterns
- Use Shadcn/UI components when applicable
- Use clear, descriptive commit messages
- Reference issues in commits when applicable (e.g.,
Fix #123) - Keep commits focused on a single change
Feel free to open an issue with your question or reach out to the maintainers.
Thank you for contributing!