363 lines
14 KiB
Markdown
363 lines
14 KiB
Markdown
# Contributing
|
|
|
|
Thanks for your interest in contributing to DiceBear.
|
|
|
|
This is the main monorepo: the JavaScript, PHP, Python, Rust, Go, and Dart core
|
|
libraries, the CLI, the docs site, and the editor all live here. Repositories
|
|
covering the JSON Schema, the avatar style definitions, the HTTP API, and the
|
|
Figma exporter are separate and each have their own `CONTRIBUTING.md`:
|
|
|
|
- [`dicebear/schema`](https://github.com/dicebear/schema/blob/main/CONTRIBUTING.md):
|
|
JSON Schema for definitions and options
|
|
- [`dicebear/styles`](https://github.com/dicebear/styles/blob/main/CONTRIBUTING.md):
|
|
Official avatar style definitions
|
|
- [`dicebear/api`](https://github.com/dicebear/api/blob/main/CONTRIBUTING.md):
|
|
Self-hostable HTTP API
|
|
- [`dicebear/exporter-plugin-for-figma`](https://github.com/dicebear/exporter-plugin-for-figma/blob/main/CONTRIBUTING.md):
|
|
Figma plugin
|
|
|
|
If your contribution belongs to one of those repos, read its file first. The
|
|
instructions below only cover this monorepo.
|
|
|
|
## Before you start
|
|
|
|
- Bug fixes, small improvements, new tests: open a pull request against the
|
|
branch that matches the target major (for DiceBear 10 that's `10.x`; the
|
|
current stable line lives on `9.x`).
|
|
- New avatar styles: contribute them to
|
|
[`dicebear/styles`](https://github.com/dicebear/styles), not here. The
|
|
walkthrough is in
|
|
[Create an avatar style with Figma](https://www.dicebear.com/guides/create-an-avatar-style-with-figma/)
|
|
or
|
|
[Create an avatar style from scratch](https://www.dicebear.com/guides/create-an-avatar-style-from-scratch/).
|
|
- Larger changes (new public API, rendering behavior, breaking changes): open an
|
|
issue first so we can agree on scope before you spend time on it.
|
|
- Security issues go privately to <contact@dicebear.com>; never file a public
|
|
issue for a vulnerability.
|
|
- Contributors follow the
|
|
[DiceBear Code of Conduct](https://github.com/dicebear/.github/blob/main/CODE_OF_CONDUCT.md).
|
|
|
|
## Requirements
|
|
|
|
- [Node.js](https://nodejs.org/) 20 or newer (CI runs on 20, 22, 24, 25)
|
|
- npm 11 (this repo pins `packageManager` in `package.json`; use
|
|
[Corepack](https://nodejs.org/api/corepack.html) if you don't already)
|
|
- For PHP work: PHP 8.2+ and Composer, plus `vendor/bin/phpunit` via
|
|
`composer install` inside `src/php/core/`
|
|
- For Python work: Python 3.10+ (CI runs on 3.10 to 3.14); install the package
|
|
in a virtualenv with `pip install -e ".[dev]"` inside `src/python/core/`
|
|
- For Rust work: Rust 1.80+ with the `clippy` and `rustfmt` components; build
|
|
and test with `cargo test`, `cargo clippy`, and `cargo fmt` inside
|
|
`src/rust/core/`
|
|
- For Go work: Go 1.23+ (CI runs on 1.23 to 1.25); test with `go test ./...` and
|
|
check with `gofmt -l .` and `go vet ./...` inside `src/go/core/`
|
|
- For Dart work: Dart SDK 3.4+ (CI runs on 3.4 and stable); test with
|
|
`dart test` and check with `dart format --output=none --set-exit-if-changed .`
|
|
and `dart analyze --fatal-infos` inside `src/dart/core/`
|
|
|
|
## Local setup
|
|
|
|
```sh
|
|
git clone git@github.com:dicebear/dicebear.git
|
|
cd dicebear
|
|
npm install
|
|
```
|
|
|
|
The monorepo uses npm workspaces (`src/js/*` and `apps/*`) driven by
|
|
[Turborepo](https://turborepo.com/). A single install at the root is enough; do
|
|
not `npm install` inside individual packages.
|
|
|
|
## Common scripts
|
|
|
|
Run these from the repo root:
|
|
|
|
| Script | What it does |
|
|
| ---------------------- | --------------------------------------------------- |
|
|
| `npm run build` | Builds every workspace via Turbo |
|
|
| `npm test` | Runs every workspace's test target |
|
|
| `npm run test:scripts` | Runs the repo-level scripts in `tests/**/*.test.js` |
|
|
| `npm run lint` | Runs ESLint across the repo with caching |
|
|
| `npm run lint:fix` | Same, with `--fix` |
|
|
| `npm run prettier` | Formats `src/` and `apps/` with Prettier |
|
|
|
|
For faster loops, scope to a single workspace:
|
|
|
|
```sh
|
|
npm run build --workspace @dicebear/core
|
|
npm run test --workspace @dicebear/core
|
|
```
|
|
|
|
## Repository layout
|
|
|
|
```
|
|
src/
|
|
├── js/ # TypeScript packages published to npm
|
|
│ ├── core/ # Rendering engine (@dicebear/core)
|
|
│ ├── cli/ # Command-line interface
|
|
│ └── converter/ # SVG → raster converter
|
|
├── php/ # PHP port (Composer package `dicebear/core`)
|
|
├── python/ # Python port (PyPI package `dicebear-core`)
|
|
├── rust/ # Rust port (crates.io crate `dicebear-core`)
|
|
├── go/ # Go port (module `github.com/dicebear/dicebear-go/v10`)
|
|
└── dart/ # Dart port (pub.dev package `dicebear_core`)
|
|
apps/
|
|
├── docs/ # VitePress documentation site (dicebear.com), including the Playground
|
|
└── editor/ # The in-browser editor (editor.dicebear.com)
|
|
tests/
|
|
└── fixtures/parity/ # Cross-language parity fixtures (see below)
|
|
scripts/
|
|
└── version.mjs # Bumps versions across the workspace and tags
|
|
```
|
|
|
|
## Working on a package
|
|
|
|
### TypeScript packages (`src/js/*`)
|
|
|
|
```sh
|
|
npm run build --workspace <package>
|
|
npm run test --workspace <package>
|
|
```
|
|
|
|
If you are working on the CLI, call the compiled script directly once you've
|
|
built it:
|
|
|
|
```sh
|
|
node src/js/cli/bin/index.js <command>
|
|
```
|
|
|
|
### PHP core (`src/php/core/`)
|
|
|
|
```sh
|
|
cd src/php/core
|
|
composer install
|
|
vendor/bin/phpunit
|
|
```
|
|
|
|
### Python core (`src/python/core/`)
|
|
|
|
```sh
|
|
cd src/python/core
|
|
pip install -e ".[dev]" # in a virtualenv
|
|
ruff check .
|
|
ruff format --check .
|
|
mypy src
|
|
pytest
|
|
```
|
|
|
|
The Python core reads the two draft-07 schemas from the `dicebear-schema`
|
|
package (the Python counterpart of `@dicebear/schema` / `dicebear/schema`),
|
|
which `pip install -e ".[dev]"` pulls in as a runtime dependency. Nothing is
|
|
vendored.
|
|
|
|
### Rust core (`src/rust/core/`)
|
|
|
|
```sh
|
|
cd src/rust/core
|
|
cargo test
|
|
cargo clippy --all-targets --all-features -- -D warnings
|
|
cargo fmt --check
|
|
```
|
|
|
|
The Rust core reads the two draft-07 schemas from the `dicebear-schema` crate
|
|
(the Rust counterpart of `@dicebear/schema` / `dicebear/schema`) as a runtime
|
|
dependency. Nothing is vendored.
|
|
|
|
### Go core (`src/go/core/`)
|
|
|
|
```sh
|
|
cd src/go/core
|
|
go test ./...
|
|
go vet ./...
|
|
gofmt -l . # prints nothing when formatting is correct
|
|
```
|
|
|
|
The Go core reads the two draft-07 schemas from the `github.com/dicebear/schema`
|
|
module (the Go counterpart of `@dicebear/schema` / `dicebear/schema`) as a
|
|
dependency. Nothing is vendored. Style definitions come from
|
|
`github.com/dicebear/styles/v10`. The module path carries the major version
|
|
(`/v10`), so a major bump changes the path by hand; `scripts/version.mjs` only
|
|
creates the Git tag the module proxy reads.
|
|
|
|
### Dart core (`src/dart/core/`)
|
|
|
|
```sh
|
|
cd src/dart/core
|
|
dart pub get
|
|
dart test
|
|
dart analyze --fatal-infos
|
|
dart format --output=none --set-exit-if-changed .
|
|
```
|
|
|
|
The Dart core reads the two draft-07 schemas from the `dicebear_schema` package
|
|
(the Dart counterpart of `@dicebear/schema` / `dicebear/schema`) as a runtime
|
|
dependency, validated with `package:json_schema`. Nothing is vendored.
|
|
|
|
`src/dart/core/CHANGELOG.md` is a git-ignored build artifact: pub.dev expects a
|
|
changelog inside the package directory, so the CI workflows copy the repository
|
|
root `CHANGELOG.md` there before `dart pub publish [--dry-run]`. This follows
|
|
the same pattern as the generated `LICENSE` copies in the styles and schema
|
|
repositories. Maintain the root changelog only.
|
|
|
|
### Cross-language parity
|
|
|
|
Every port must produce output **byte-identical** to the reference JavaScript
|
|
core (`@dicebear/core`) for the same inputs. A shared fixture suite in
|
|
`tests/fixtures/parity/` (generated from the JS reference) enforces this, and
|
|
each side consumes it:
|
|
|
|
- JS side: `src/js/core/tests/Parity.test.js`, run via
|
|
`npm run test --workspace @dicebear/core`.
|
|
- PHP side: `tests/ParityTest.php`, run via `vendor/bin/phpunit` in
|
|
`src/php/core/`.
|
|
- Python side: `tests/test_parity.py`, run via `pytest` in `src/python/core/`.
|
|
- Rust side: the module unit tests plus `tests/avatars.rs`, run via `cargo test`
|
|
in `src/rust/core/`.
|
|
- Go side: the in-package tests (`parity_test.go`, `avatars_test.go`), run via
|
|
`go test ./...` in `src/go/core/`.
|
|
- Dart side: the tests under `test/parity/`, run via `dart test` in
|
|
`src/dart/core/`.
|
|
|
|
The fixtures cover `Fnv1a` (hash + hex), `Mulberry32` (chained sequences), every
|
|
`Prng` method, number-to-string formatting (`numbers.json`, the `formatNumber`
|
|
contract: every emitted number rounded to at most 5 decimal places, which every
|
|
port must reproduce identically), initials extraction, the `Color` helpers
|
|
(including bit-exact luminance doubles), accept/reject validation outcomes plus
|
|
circular color-reference chains, the `OptionsDescriptor` field map, and full
|
|
`Avatar.toString()` (plus selected `toDataUri()`) output for the `initials`,
|
|
`thumbs`, `glass`, `notionists`, and `shape-grid` styles. That last bucket
|
|
covers seed, size, transforms, gradients, `title` escaping, and
|
|
component-variant overrides.
|
|
|
|
Float determinism is part of the parity contract: `pow` is not correctly rounded
|
|
(JS engines, libm, and Go all differ in the last bit), so the sRGB linearization
|
|
ships as a precomputed 256-entry table in every port, and languages that fuse
|
|
multiply-add into FMA instructions (e.g. Go on arm64) must force intermediate
|
|
rounding. The details live in the
|
|
[Implement DiceBear Core](https://www.dicebear.com/specification/implement-dicebear-core/)
|
|
spec.
|
|
|
|
If your change affects rendering or the PRNG in `@dicebear/core`, regenerate the
|
|
fixtures from the JS reference and commit the diff:
|
|
|
|
```sh
|
|
npm run fixtures:parity
|
|
```
|
|
|
|
The PHP, Python, Rust, Go, and Dart suites will then fail loudly until those
|
|
sides are brought back in sync. That is the intended signal. If you only intend
|
|
to touch one language, expect to update both before your PR can be merged.
|
|
|
|
When porting DiceBear to another language, run these fixtures against your
|
|
implementation to prove it conforms. See
|
|
[Implement DiceBear Core](https://www.dicebear.com/specification/implement-dicebear-core/)
|
|
for the full spec.
|
|
|
|
## Documentation changes (`apps/docs/`)
|
|
|
|
The docs site is a VitePress app under `apps/docs/`.
|
|
|
|
```sh
|
|
npm run dev --workspace @dicebear/docs # live reload on localhost
|
|
npm run build --workspace @dicebear/docs # production build check
|
|
```
|
|
|
|
For larger editorial changes, open a draft PR early so reviewers can follow
|
|
along.
|
|
|
|
## Editor changes (`apps/editor/`)
|
|
|
|
The editor is the standalone app served at
|
|
[editor.dicebear.com](https://editor.dicebear.com).
|
|
|
|
```sh
|
|
npm run dev --workspace @dicebear/editor
|
|
npm run build --workspace @dicebear/editor
|
|
```
|
|
|
|
## Code style
|
|
|
|
- ESLint and Prettier decide what counts as correctly formatted code; run
|
|
`npm run lint` and `npm run prettier` before you open a PR.
|
|
- TypeScript is `strict`. Prefer narrow types to `any` / `unknown` casts.
|
|
- PHP code follows PSR-12; `vendor/bin/phpunit` and Composer's built-in scripts
|
|
catch the rest.
|
|
- Python code is formatted and linted with [Ruff](https://docs.astral.sh/ruff/)
|
|
and type-checked with `mypy` in strict mode; run `ruff check .`,
|
|
`ruff format .`, and `mypy src` in `src/python/core/` before you open a PR.
|
|
- Rust code is formatted with `rustfmt` and linted with Clippy (warnings
|
|
denied); run `cargo fmt` and
|
|
`cargo clippy --all-targets --all-features -- -D warnings` in `src/rust/core/`
|
|
before you open a PR.
|
|
- Go code is formatted with `gofmt` and vetted with `go vet`; run `gofmt -w .`
|
|
and `go vet ./...` in `src/go/core/` before you open a PR.
|
|
- Dart code is formatted with `dart format` and analyzed with
|
|
`dart analyze --fatal-infos` (lints from `analysis_options.yaml`); run both in
|
|
`src/dart/core/` before you open a PR.
|
|
|
|
## Releasing (maintainers only)
|
|
|
|
> Only maintainers with write access can release new versions. This section is
|
|
> documented here for completeness.
|
|
|
|
Releases are triggered by Git tags. The version script updates every package in
|
|
the workspace, creates a commit, and creates the tag:
|
|
|
|
```sh
|
|
node scripts/version.mjs <version>
|
|
```
|
|
|
|
The version must be a valid [semver](https://semver.org/) value (e.g. `10.1.0`
|
|
or `10.2.0-alpha.1`). The script will:
|
|
|
|
1. Update `version` in every `package.json` across the workspace
|
|
2. Update internal workspace dependency references
|
|
3. Update `version` in `src/python/core/pyproject.toml`,
|
|
`src/rust/core/Cargo.toml`, and `src/dart/core/pubspec.yaml` (the Python,
|
|
Rust, and Dart cores are not npm workspaces, so they are bumped explicitly to
|
|
stay in lockstep)
|
|
4. Sync `package-lock.json`
|
|
5. Create a Git commit and tag (e.g. `v10.1.0`)
|
|
|
|
Push the commit and the tag:
|
|
|
|
```sh
|
|
git push && git push --tags
|
|
```
|
|
|
|
The tag triggers the
|
|
[Publish](https://github.com/dicebear/dicebear/actions/workflows/publish.yml)
|
|
workflow, which:
|
|
|
|
1. Runs the test suite on Node 20, 22, 24, and 25
|
|
2. Builds every package
|
|
3. Picks the npm dist-tag:
|
|
- Tags containing `alpha`, `beta`, or `rc` go out as `next`
|
|
- Other tags on the default branch go out as `latest`
|
|
- Other tags on any other branch go out as `v<major>-lts` (so that
|
|
backporting a patch to `9.x` after `10.x` has shipped does not overwrite
|
|
`latest`)
|
|
4. Publishes all changed packages to npm via `scripts/publish.mjs`
|
|
5. Builds `src/python/core` and publishes `dicebear-core` to PyPI via trusted
|
|
publishing (the `publish-python` job): no token and no separate repository,
|
|
the same way the npm packages go out
|
|
6. Publishes the Rust core `dicebear-core` to crates.io via trusted publishing
|
|
(the `publish-rust` job): likewise no token; `cargo publish` builds and
|
|
uploads `src/rust/core` in one step
|
|
7. Publishes the Dart core `dicebear_core` to pub.dev via automated publishing
|
|
(the `publish-pub` job): likewise no token; pub.dev verifies the `v<version>`
|
|
tag against the pubspec version and publishes `src/dart/core` straight from
|
|
the monorepo
|
|
|
|
The PHP port is the exception: Composer/Packagist consumes one Git repository
|
|
per package rather than a monorepo subdirectory, so `split-php-core.yml` mirrors
|
|
`src/php/core` (tags included) to the standalone
|
|
[`dicebear/dicebear-php`](https://github.com/dicebear/dicebear-php) repository,
|
|
and Packagist publishes `dicebear/core` from that mirror. All five ports ride
|
|
the same version the monorepo tagged.
|
|
|
|
## Licensing
|
|
|
|
By opening a pull request you agree that your contribution is released under the
|
|
repository's [MIT license](./LICENSE). Avatar style artwork in `dicebear/styles`
|
|
may carry other licenses; see that repo's `LICENSE.md` for details.
|