The Buffy Book
Buffy is a Protocol Buffers build and publishing tool. From a
single set of .proto files, Buffy generates idiomatic libraries for multiple
languages, packages each one according to its ecosystem’s conventions, and
publishes them to their respective registries (e.g. crates.io, Maven Central, npm)
or to Git. You can contribute to this book on GitHub.
Sections
Install Buffy and set up your first project that generates protocol buffer libraries for multiple languages.
The reference covers the details of every area of Buffy: the manifest format, profile configuration per language, environment variables, and the command-line interface.
Appendices:
Getting Started
To get started with Buffy, install it and set up your first project that generates protocol buffer libraries for multiple languages.
- Installation — Install Buffy on your system.
- First Steps with Buffy — Create a project and generate your first multi-language libraries.
Installation
Buffy is distributed as a single binary. Install it with:
curl -sSL https://pkgs.julian-siebert.de/buffy/install.sh | sh
The installer places the buffy binary in ~/.local/bin/buffy and prints
instructions if that directory is not already on your PATH.
Verify the installation:
buffy --version
External tools
Buffy generates code by invoking language-specific tools. You only need the tools for the languages you actually target — Buffy reports a clear diagnostic if a tool is missing when you build.
A complete list per language is documented in each profile chapter under the Buffy Reference. The most common tools:
protoc— The Protocol Buffers compiler. Required by every language.git— Required by everygitprofile variant and by Go modules.- Per language:
go,cargo,mvn,npm, plus their respective protobuf plugins.
To verify the toolchain for a project after configuring it, run:
buffy check
Updating
Re-running the installer updates Buffy to the latest version:
curl -sSL https://pkgs.julian-siebert.de/buffy/install.sh | sh
Uninstalling
Remove the binary:
rm ~/.local/bin/buffy
First Steps with Buffy
This walkthrough creates a small Buffy project, defines a .proto schema, and
generates libraries for two languages - Go and Rust - using the git variant
so no registry accounts are needed.
Create a project
Create a directory for your project and add the standard layout:
mkdir tomato && cd tomato
mkdir proto .buffy
Define the manifest
Create a Buffy.toml at the project root:
[package]
name = "tomato"
description = "Tomato protocol buffers"
version = "0.1.0"
license = "MIT"
homepage = "https://github.com/example/tomato"
authors = ["Jane Doe <jane@example.com>"]
[source]
path = "proto"
See The Manifest Format for all available fields.
Add a .proto file
Create proto/greeter.proto:
syntax = "proto3";
package greeter;
service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply);
}
message HelloRequest {
string name = 1;
}
message HelloReply {
string message = 1;
}
Configure profiles
Create one profile file per language target.
.buffy/golang.toml:
[golang.git]
module = "github.com/example/tomato-go"
remote = "git@github.com:example/tomato-go.git"
branch = "main"
grpc = true
.buffy/rust.toml:
[rust.git]
name = "tomato"
edition = "2021"
remote = "git@github.com:example/tomato-rs.git"
branch = "main"
repository = "https://github.com/example/tomato-rs"
documentation = "https://github.com/example/tomato-rs"
grpc = true
The filename (e.g., golang.toml) becomes the profile name and shows up in
build output. See Profiles Format for the full
list of options per language.
Verify the toolchain
Before building, check that all required tools are installed:
buffy check
If anything is missing, Buffy prints an installation hint specific to your platform.
Build
Run Buffy without arguments to build every profile in parallel:
buffy
Each profile produces a complete, self-contained package under target/:
Buffy Reference
The reference covers the details of various areas of Buffy.
- The Manifest Format — The
Buffy.tomlpackage manifest. - Profiles Format — Per-language target configuration in
.buffy/.- Golang — Publish Go modules via Git.
- Java — Publish to Maven Central or via Git.
- Kotlin — Publish to Maven Central or via Git.
- Rust — Publish to crates.io or via Git.
- JavaScript — Publish to npm or via Git.
- TypeScript — Publish to npm or via Git.
- Python — Publish to Pypi or via Git.
- Environment Variables — Credentials and configuration via environment.
- Command-Line Interface — The
buffyCLI.
The Manifest Format
The Buffy.toml file for each package is called its manifest. It is written
in the [TOML] format. It contains metadata that is needed to compile and publish
the protocol buffers to all supported language targets.
Every manifest file consists of the following sections:
[package]— Defines a package.[source]— Configures where.protofiles live.path— Path to the directory containing.protofiles.
Profile configuration (the per-language publishing targets in .buffy/) is
documented separately in the profiles chapter.
The [package] section
The first section in a Buffy.toml is [package].
[package]
name = "tomato" # the name of the package
version = "0.1.0" # the current version, obeying semver
All fields in this section are required. The metadata defined here is embedded
in every published artifact across all language targets (e.g., it ends up in
Cargo.toml, the Maven POM, package.json, and the AUTHORS/LICENSE files of
generated Go modules).
The name field
The package name is an identifier used to refer to the package. It serves as
the default base name for language-specific artifacts (Cargo crate name, Maven
artifactId, npm package name, etc.). Profiles may override the name per
language.
The name must use only alphanumeric characters or - or _, and cannot be
empty.
- Only ASCII characters are allowed.
- Use a maximum of 32 characters of length.
[package]
name = "tomato"
The version field
The version field is formatted according to the SemVer specification:
Versions must have three numeric parts: the major version, the minor version, and the patch version.
A pre-release part can be added after a dash such as 1.0.0-alpha. The
pre-release part may be separated with periods to distinguish separate
components. Numeric components will use numeric comparison while everything
else will be compared lexicographically. For example, 1.0.0-alpha.11 is
higher than 1.0.0-alpha.4.
A metadata part can be added after a plus, such as 1.0.0+21AF26D3. This is
for informational purposes only and is generally ignored.
[package]
# ...
version = "0.1.0"
The version applies to all language targets when publishing. To override the
version for a single run (e.g., in CI where it is derived from a Git tag), use
the --publish-version flag:
buffy --publish --publish-version 1.2.3
The description field
The description is a short blurb about the package. Registries that display it (such as crates.io for the Rust target or npmjs.com for the JavaScript and TypeScript targets) will show this text on the package page. This should be plain text (not Markdown).
[package]
# ...
description = "Tomato protocol buffers for the salad service"
The license field
The license field contains the SPDX expression of the software license that
the package is released under.
The value is interpreted as an SPDX 2.3 license expression. The name must be
a known license from the SPDX license list. SPDX expressions support AND
and OR operators to combine multiple licenses.
[package]
# ...
license = "MIT OR Apache-2.0"
Using OR indicates the user may choose either license. Using AND indicates
the user must comply with both licenses simultaneously. Some examples:
MITMIT OR Apache-2.0LGPL-2.1-only AND MIT AND BSD-2-Clause
When the manifest declares multiple licenses, Buffy generates one
LICENSE-<id> file per license plus an index LICENSE file describing the
combination. With a single license, a single LICENSE file is generated. The
full license text is embedded from the SPDX database.
Custom LicenseRef-* identifiers are not supported.
The homepage field
The homepage field should be a URL to a site that is the home page for your
package.
[package]
# ...
homepage = "https://github.com/example/tomato"
The homepage URL is surfaced in language-specific metadata fields where
applicable: homepage in Cargo.toml, <url> in the Maven POM, homepage in
package.json.
The authors field
The authors field lists the people or organizations that are considered the
authors of the package. An optional email address may be included within
angled brackets at the end of each author entry.
[package]
# ...
authors = [
"Jane Doe <jane@example.com>",
"John Smith",
"Acme Corp <opensource@acme.com>",
]
Each entry is parsed into a name and an optional email. Both are then forwarded
to language-specific metadata: <developer> entries in the Maven POM, the
author field in package.json, the authors array in Cargo.toml, and an
AUTHORS file in generated Go modules.
If an entry is malformed (e.g., empty, or containing brackets without an email), Buffy reports a diagnostic pointing at the offending entry.
The [source] section
The [source] section tells Buffy where to find the .proto files for code
generation.
[source]
path = "proto"
If the section is omitted, the default is used:
[source]
path = "src"
The path field
The relative path (from Buffy.toml) to the directory containing .proto
files. Buffy walks this directory recursively and passes every .proto file
it finds to protoc.
The path is also used as --proto_path when invoking protoc, so imports
between .proto files should be relative to this root.
[source]
path = "proto"
Profiles Format
Profiles configure language-specific publishing targets. They live in the
.buffy/ directory next to Buffy.toml, with one TOML file per profile.
my-project/
├── Buffy.toml
├── proto/
│ └── greeter.proto
└── .buffy/
├── golang-github.toml
├── java.toml
├── kotlin.toml
├── rust-crates-io.toml
├── js.toml
└── typescript.toml
The filename (without .toml) becomes the profile name. The profile name
is used as the directory under target/ where build artifacts are written
(e.g., target/golang-github/, target/rust-crates-io/) and as the prefix in progress
output.
Profile naming
Profile names must be unique within a project. Buffy reports an error if two
files in .buffy/ resolve to the same name (e.g., on case-insensitive
filesystems where Rust.toml and rust.toml collide).
The filename has no relationship to the language the profile configures —
the language is selected by the table syntax inside the file. A profile
called internal-go.toml can configure a golang target, and a project may
contain multiple profiles for the same language under different names (for
example, one for an internal Git remote and another for a public registry).
File structure
Each profile file selects exactly one language and exactly one publishing variant, using nested-table syntax:
[<language>.<variant>]
# fields specific to the language and variant
For example, a Rust profile that publishes to crates.io:
[rust.crate]
name = "tomato"
edition = "2021"
repository = "https://github.com/example/tomato"
documentation = "https://docs.rs/tomato"
registry = "crates-io"
grpc = true
A Go profile that publishes via Git:
[golang.git]
module = "github.com/example/tomato-go"
remote = "git@github.com:example/tomato-go.git"
branch = "main"
grpc = true
keep = ["README.md"]
A profile file must contain exactly one [<language>.<variant>] table.
Multiple variants in the same file are not supported; use multiple profile
files instead.
Available languages and variants
| Language | Variants | Default destination |
|---|---|---|
golang | git | A Git remote (Go modules use Git tags) |
java | maven_central, git | Sonatype Central Portal |
kotlin | maven_central, git | Sonatype Central Portal |
rust | crate, git | crates.io or another Cargo registry |
javascript | npm, git | npmjs.org or any npm-compatible registry |
typescript | npm, git | npmjs.org or any npm-compatible registry |
python | pypi, git | pypi.org |
Each language is documented in its own chapter, with the full list of fields per variant.
The git variant
Every language supports a git variant. Instead of publishing to a registry,
Buffy commits the generated artifact to a Git repository and tags the commit
with v<version>. Consumers depend on the package by its Git URL.
Common fields across all git variants:
remote— The Git URL the artifact is pushed to. SSH URLs are recommended; Buffy disables Git’s terminal prompt during operations, so HTTPS URLs work only if credentials are pre-cached.branch— The branch to push to. The previous content is replaced (force-push), with the exception of files listed inkeep.keep— A list of paths to fetch from the remote before committing, preserving them across publishes. Useful for human-maintained files such asREADME.mdthat should not be overwritten by code generation.
The exact list of fields varies by language because the build artifact
itself is language-specific (a Cargo.toml, a Maven POM, a package.json,
etc.) and those fields are part of the profile.
Build output
Each profile is built into its own directory under target/:
target/
├── golang/ # output of the `golang.toml` profile
├── java/ # output of the `java.toml` profile
└── ...
The directory is cleared at the start of each build, so all generated content
reflects the current run. Buffy automatically adds target/ to a .gitignore
at the repository root.
Parallelism
All profiles are built in parallel. With --publish, the publish step is
also parallel. If any profile fails, Buffy continues with the others and
reports a combined diagnostic at the end of the run, listing every profile
that succeeded and every profile that failed with its underlying error.
Environment variables
Most variants require credentials to publish, supplied via environment variables. See Environment Variables for the complete list.
Golang Profiles
The golang profile generates a Go module from your .proto files and
publishes it to a Git repository. Go modules are versioned by Git tags, so
the consuming side only needs go get <module>@<tag>.
Available variants:
git— Push the generated module to a Git remote.
Required tools
protoc— Protocol Buffers compiler.protoc-gen-go— Go code generator plugin.protoc-gen-go-grpc— gRPC plugin (only whengrpc = true).go— Go toolchain (used forgo mod init,go mod tidy,go build).git— Used to commit, tag, and push the generated module.
buffy check verifies that all of these are installed and on the PATH,
emitting installation hints if anything is missing.
The git variant
Generates the Go module under target/<profile>/, runs go mod init and
go mod tidy to populate go.sum, then commits the result and pushes it
to the configured remote with a v<version> tag.
Example
# .buffy/golang.toml
[golang.git]
module = "github.com/example/tomato-go"
remote = "git@github.com:example/tomato-go.git"
branch = "main"
grpc = true
keep = ["README.md"]
Fields
module— The Go module path.remote— Git remote URL.branch— Branch to push to.grpc— Whether to generate gRPC service stubs.keep— Files to preserve across publishes.
The module field
The Go module path, as it will appear in go.mod and as consumers will use
it in their import statements. Conventionally matches the host and path of
the remote.
module = "github.com/example/tomato-go"
This value is passed to protoc-gen-go as --go_opt=module=... so that the
generated package paths are rewritten correctly.
The remote field
The Git URL the generated module is pushed to. SSH URLs are recommended because Buffy disables Git’s terminal prompt; HTTPS URLs work only if credentials are pre-cached or supplied via a credential helper.
remote = "git@github.com:example/tomato-go.git"
The branch field
The branch to push to. Buffy force-pushes the generated content to this
branch on every publish; the previous content is replaced (with the
exception of files listed in keep).
branch = "main"
The grpc field
When true, Buffy invokes protoc-gen-go-grpc in addition to
protoc-gen-go, generating service stubs alongside the message types. When
omitted or false, only message types are generated.
grpc = true
Default: false.
The keep field
A list of file paths (relative to the repository root) that Buffy fetches
from the remote before committing. Useful for human-maintained files like
README.md that should outlive the regenerated content.
keep = ["README.md", "docs/usage.md"]
If a listed file does not yet exist on the remote, Buffy logs a notice and skips it instead of failing.
Default: [] (no files preserved).
Example consumer usage
go get github.com/example/tomato-go@v0.1.0
import (
pb "github.com/example/tomato-go/greeter"
)
Java Profiles
The java profile generates a Maven project from your .proto files using
protoc-gen-java and publishes it either to a Git repository or to
Maven Central via the Sonatype Central Portal. Consumers depend on the
artifact through a normal Maven (or Gradle) coordinate.
Available variants:
maven_central— Publish to Maven Central.git— Push the generated Maven project to a Git remote.
Required tools
protoc— Protocol Buffers compiler.java— A JDK (11 or newer recommended), used by Maven.mvn— Apache Maven, used to compile and (formaven_central) deploy.gpg— GnuPG, used to sign artifacts. Required for themaven_centralvariant; not used by thegitvariant.git— Required only for thegitvariant.
buffy check verifies that the relevant tools are installed for the configured
variant.
The maven_central variant
Generates a Maven project under target/<profile>/, runs mvn compile to
verify, signs the artifacts with GPG, and uploads them to the Sonatype Central
Portal via the central-publishing-maven-plugin.
The Sonatype namespace must be verified for your account before the first
publish (e.g., io.github.<your-github-user> is verified by creating a
public repository named after a verification code Sonatype gives you).
Example
# .buffy/java-example.toml
[java.maven_central]
group_id = "io.github.example"
artifact_id = "tomato"
url = "https://github.com/example/tomato"
auto_publish = false
wait_until = "uploaded"
[java.maven_central.scm]
connection = "scm:git:git://github.com/example/tomato.git"
url = "https://github.com/example/tomato"
Fields
group_id— Maven group ID.artifact_id— Maven artifact ID.url— Project URL embedded in the POM.scm— Source-control coordinates required by Maven Central.protobuf_version— Pin theprotobuf-javaruntime version.auto_publish— Auto-release after upload validates.wait_until— How far to wait in the publishing pipeline.
The group_id field
The Maven groupId of the published artifact. Must match a namespace
verified for your Sonatype account.
group_id = "io.github.example"
The artifact_id field
The Maven artifactId of the published artifact.
artifact_id = "tomato"
The url field
A project URL written to the <url> tag in the generated POM. Maven Central
requires this field to be present.
url = "https://github.com/example/tomato"
The scm section
Source-control coordinates required by Maven Central. Embedded in the
<scm> block of the POM.
[java.maven_central.scm]
connection = "scm:git:git://github.com/example/tomato.git"
url = "https://github.com/example/tomato"
connection— The SCM connection string, conventionallyscm:git:<git-url>.url— A browsable URL for the source repository.
The protobuf_version field
Optional. Pin a specific version of the com.google.protobuf:protobuf-java
runtime dependency. If omitted, Buffy queries Maven Central for the latest
release version.
protobuf_version = "4.29.3"
The auto_publish field
When true, the Sonatype Central Portal automatically releases the artifact
after validation succeeds. When false, the artifact lands in the “Validated”
state in the portal and must be released manually.
auto_publish = false
Default: false. For first releases, leave this off so you can manually
verify the upload.
The wait_until field
Controls how long the publish step waits before returning. One of:
| Value | Waits for | Typical duration |
|---|---|---|
uploaded | Upload to Sonatype completes | seconds |
validated | Sonatype validation (schema, GPG, etc.) | 1–3 minutes |
published | Indexing into Maven Central completes | 10–30 minutes |
wait_until = "uploaded"
Default: uploaded.
Required environment variables
| Variable | Purpose |
|---|---|
MAVEN_USERNAME | Sonatype Central Portal username token |
MAVEN_PASSWORD | Sonatype Central Portal password token |
GPG_KEY_ID | GPG key ID used to sign the artifacts |
GPG_PASSPHRASE | Passphrase for the GPG key |
GPG_PRIVATE_KEY | Optional: armored private key, for CI runners |
See Environment Variables for details.
Example consumer usage
<dependency>
<groupId>io.github.example</groupId>
<artifactId>tomato</artifactId>
<version>0.1.0</version>
</dependency>
The git variant
Generates the same Maven project as maven_central, but commits and tags it
to a Git repository instead of uploading. No GPG signing, no Sonatype account.
Useful for internal sharing or quick prototypes.
Example
# .buffy/java.toml
[java.git]
group_id = "com.example"
artifact_id = "tomato"
url = "https://github.com/example/tomato-java"
remote = "git@github.com:example/tomato-java.git"
branch = "main"
keep = ["README.md"]
[java.git.scm]
connection = "scm:git:git://github.com/example/tomato-java.git"
url = "https://github.com/example/tomato-java"
Fields
group_id— as inmaven_central.artifact_id— as inmaven_central.url— as inmaven_central.scm— as inmaven_central.protobuf_version— as inmaven_central.remote— Git URL the artifact is pushed to.branch— Branch to push to.keep— Files to preserve from the remote.
The remote field
The Git URL the generated Maven project is pushed to. SSH URLs are recommended because Buffy disables Git’s terminal prompt.
remote = "git@github.com:example/tomato-java.git"
The branch field
The branch to push to. Buffy force-pushes on every publish.
branch = "main"
The keep field
A list of file paths (relative to the repository root) to preserve across publishes by checking them out from the remote before committing.
keep = ["README.md"]
Default: [].
Example consumer usage
Maven projects can depend on a Git source via JitPack or by cloning and
running mvn install locally. Example with JitPack:
<repositories>
<repository>
<id>jitpack.io</id>
<url>https://jitpack.io</url>
</repository>
</repositories>
<dependency>
<groupId>com.github.example</groupId>
<artifactId>tomato-java</artifactId>
<version>v0.1.0</version>
</dependency>
Kotlin Profiles
The kotlin profile generates a Maven project from your .proto files. Buffy
uses protoc-gen-java to generate the message classes and packages them
together with the Kotlin standard library so the artifact can be consumed
naturally from Kotlin code. Kotlin can call Java protobuf classes directly, so
no separate Kotlin code generation is required.
Available variants:
maven_central— Publish to Maven Central.git— Push the generated Maven project to a Git remote.
Required tools
protoc— Protocol Buffers compiler.java— A JDK (11 or newer recommended).mvn— Apache Maven; the Kotlin compiler is downloaded automatically as a Maven plugin, no separatekotlinbinary is required.gpg— GnuPG, for themaven_centralvariant.git— For thegitvariant.
The maven_central variant
Generates a Maven project that compiles both Java (from protoc) and Kotlin
sources, then publishes the resulting JAR (with sources and Javadoc) to Maven
Central via the Sonatype Central Portal.
Example
# .buffy/kotlin-example.toml
[kotlin.maven_central]
group_id = "io.github.example"
artifact_id = "tomato-kotlin"
url = "https://github.com/example/tomato"
auto_publish = false
wait_until = "uploaded"
[kotlin.maven_central.scm]
connection = "scm:git:git://github.com/example/tomato.git"
url = "https://github.com/example/tomato"
Fields
group_id— Maven group ID.artifact_id— Maven artifact ID.url— Project URL embedded in the POM.scm— Source-control coordinates.protobuf_version— Pinprotobuf-javaruntime.kotlin_version— Pin Kotlin compiler version.auto_publish— Auto-release after upload validates.wait_until— How far to wait in the pipeline.
The group_id field
The Maven groupId. Must match a namespace verified for your Sonatype
account.
group_id = "io.github.example"
The artifact_id field
The Maven artifactId.
artifact_id = "tomato-kotlin"
The url field
The project URL embedded in the POM <url> tag.
url = "https://github.com/example/tomato"
The scm section
[kotlin.maven_central.scm]
connection = "scm:git:git://github.com/example/tomato.git"
url = "https://github.com/example/tomato"
The protobuf_version field
Optional. Pin a specific version of the com.google.protobuf:protobuf-java
runtime dependency. If omitted, Buffy queries Maven Central for the latest
release.
protobuf_version = "4.29.3"
The kotlin_version field
Optional. Pin a specific version of the Kotlin compiler and standard library.
If omitted, Buffy queries Maven Central for the latest release of
org.jetbrains.kotlin:kotlin-stdlib.
kotlin_version = "2.0.21"
The auto_publish field
See auto_publish in Java profiles.
The wait_until field
See wait_until in Java profiles.
Required environment variables
Same as Java.
Example consumer usage
<dependency>
<groupId>io.github.example</groupId>
<artifactId>tomato-kotlin</artifactId>
<version>0.1.0</version>
</dependency>
import io.github.example.tomato.GreeterOuterClass.HelloRequest
val req = HelloRequest.newBuilder().setName("World").build()
println(req.name)
The git variant
Like the Java git variant, but with Kotlin tooling configured in the POM.
Example
# .buffy/kotlin.toml
[kotlin.git]
group_id = "com.example"
artifact_id = "tomato-kotlin"
url = "https://github.com/example/tomato-kotlin"
remote = "git@github.com:example/tomato-kotlin.git"
branch = "main"
keep = ["README.md"]
[kotlin.git.scm]
connection = "scm:git:git://github.com/example/tomato-kotlin.git"
url = "https://github.com/example/tomato-kotlin"
Fields
The same fields as maven_central, plus:
remote— Git URL the artifact is pushed to.branch— Branch to push to.keep— Files to preserve from the remote.
These behave identically to the same-named fields in Java profiles.
Rust Profiles
The rust profile generates a Cargo crate from your .proto files using
prost and (optionally) tonic for gRPC. The crate is published either
to crates.io (or any Cargo-compatible registry) or to a Git repository.
Available variants:
Required tools
protoc— Protocol Buffers compiler.protoc-gen-prost— Rust message generator.protoc-gen-prost-crate— Crate-layout generator.protoc-gen-tonic— gRPC plugin (only whengrpc = true).cargo— Rust toolchain, used forcargo buildandcargo publish.git— Required only for thegitvariant.
The protoc-gen-* plugins are installed via Cargo:
cargo install protoc-gen-prost protoc-gen-prost-crate protoc-gen-tonic
The crate variant
Generates a Cargo crate under target/<profile>/, runs cargo build to
verify, then runs cargo publish against the configured registry.
Example
# .buffy/rust-example.toml
[rust.crate]
name = "tomato"
edition = "2021"
repository = "https://github.com/example/tomato"
documentation = "https://docs.rs/tomato"
registry = "crates-io"
grpc = true
Fields
name— Crate name as it appears inCargo.toml.edition— Rust edition.repository— Repository URL embedded in the crate.documentation— Documentation URL.registry— Cargo registry name.prost_version— Pinprostruntime.tonic_version— Pintonicruntime.grpc— Generate gRPC service stubs.
The name field
The crate name as it will appear in Cargo.toml and on the registry. Must
follow Cargo naming rules (lowercase, hyphens permitted).
name = "tomato"
The library name (i.e. the Rust module name) is derived by replacing
hyphens with underscores: tomato-rs becomes tomato_rs.
The edition field
The Rust edition for the generated crate.
edition = "2021"
Common values: "2021", "2024". The edition is written verbatim to the
generated Cargo.toml.
The repository field
A URL to the source repository. Embedded in the crate’s Cargo.toml as the
repository field.
repository = "https://github.com/example/tomato"
The documentation field
A URL to the crate’s documentation page.
documentation = "https://docs.rs/tomato"
The registry field
The name of the Cargo registry to publish to. The special value
"crates-io" targets crates.io directly. Any other value must correspond
to a registry configured in the user’s ~/.cargo/config.toml:
# ~/.cargo/config.toml
[registries]
my-registry = { index = "sparse+https://my-kellnr.example.com/api/v1/crates/" }
# .buffy/rust.toml
registry = "my-registry"
Default: "crates-io".
The prost_version field
Optional. Pin the prost runtime version. If omitted, Buffy queries
crates.io for the latest release.
prost_version = "0.13"
The tonic_version field
Optional. Pin the tonic runtime version (and tonic-prost for tonic 0.13+).
Only used when grpc = true. If omitted, Buffy queries crates.io for the
latest release.
tonic_version = "0.13"
The grpc field
When true, Buffy invokes protoc-gen-tonic and includes tonic and
tonic-prost in the crate’s dependencies.
grpc = true
Default: false.
Required environment variables
| Variable | Purpose |
|---|---|
CARGO_REGISTRY_TOKEN | Token for the configured registry (publish auth) |
For non-crates-io registries, the index URL must be configured in
~/.cargo/config.toml as shown above.
Example consumer usage
cargo add tomato
#![allow(unused)]
fn main() {
use tomato::greeter::HelloRequest;
let req = HelloRequest { name: "World".into() };
println!("{}", req.name);
}
The git variant
Generates the same crate as crate, but commits and tags it to a Git
repository instead of uploading to a registry. Useful when you want to
distribute internally without setting up a registry, or to give consumers
a reproducible Git pin.
Example
# .buffy/rust.toml
[rust.git]
name = "tomato"
edition = "2021"
remote = "git@github.com:example/tomato-rs.git"
branch = "main"
repository = "https://github.com/example/tomato-rs"
documentation = "https://github.com/example/tomato-rs"
grpc = true
keep = ["README.md"]
Fields
The same fields as crate (except registry, which doesn’t apply), plus:
remote— Git URL the crate is pushed to.branch— Branch to push to.keep— Files to preserve from the remote.
The remote field
remote = "git@github.com:example/tomato-rs.git"
The branch field
branch = "main"
The keep field
keep = ["README.md"]
Default: [].
Example consumer usage
# in the consumer's Cargo.toml
[dependencies]
tomato = { git = "https://github.com/example/tomato-rs", tag = "v0.1.0" }
JavaScript Profiles
The javascript profile generates a CommonJS JavaScript library from your
.proto files using protoc-gen-js and (optionally) gRPC-Web for
browser-compatible gRPC. The package is published either to npm or to a Git
repository.
Available variants:
npm— Publish to npm or another npm-compatible registry.git— Push the generated package to a Git remote.
Required tools
protoc— Protocol Buffers compiler.protoc-gen-js— JavaScript code generator.protoc-gen-grpc-web— gRPC-Web plugin (only whengrpc = true).node— Node.js runtime.npm— npm CLI, used to install dependencies and publish.git— Required only for thegitvariant.
The npm variant
Generates a JavaScript package under target/<profile>/, validates the
package.json by running npm install and npm publish --dry-run, then
publishes via npm publish.
Example
# .buffy/js-example.toml
[javascript.npm]
name = "@example/tomato"
registry = "https://registry.npmjs.org/"
access = "public"
repository = "https://github.com/example/tomato"
homepage = "https://github.com/example/tomato"
grpc = true
Fields
name— npm package name.registry— Registry URL.access—"public"or"restricted"for scoped packages.repository— Repository URL embedded in the package.homepage— Override the global homepage.grpc— Generate gRPC-Web service stubs.
The name field
The npm package name. Scoped packages (e.g. @example/tomato) are supported.
name = "@example/tomato"
The registry field
Optional. The npm registry URL to publish to. Examples:
"https://registry.npmjs.org/"— the public npm registry."https://npm.pkg.github.com/"— GitHub Packages."http://localhost:4873/"— a local Verdaccio instance for testing.
registry = "https://registry.npmjs.org/"
Default: "https://registry.npmjs.org/".
The access field
Optional. Controls the access level for scoped packages:
"public"— the package is publicly readable."restricted"— the package requires authentication to read (paid npm feature).
For unscoped packages, the value has no effect.
access = "public"
Default: "public".
The repository field
A URL to the source repository, embedded in package.json as the
repository.url field.
repository = "https://github.com/example/tomato"
The homepage field
Optional. Overrides the package’s homepage URL. If omitted, the value from
the global [package] section’s homepage field is used.
homepage = "https://example.com/tomato"
The grpc field
When true, Buffy invokes protoc-gen-grpc-web to generate browser-compatible
gRPC-Web service stubs and includes grpc-web in the package’s dependencies.
grpc = true
Default: false.
Required environment variables
| Variable | Purpose |
|---|---|
NPM_TOKEN | Auth token for the configured registry |
The token can come from npm token create (npm) or the corresponding
mechanism for your registry (Verdaccio, GitHub Packages, etc.).
Example consumer usage
npm install @example/tomato
const { HelloRequest } = require("@example/tomato/greeter_pb");
const req = new HelloRequest();
req.setName("World");
console.log(req.getName());
The git variant
Generates the same JavaScript package as npm, but commits and tags it to a
Git repository instead of uploading to a registry.
Example
# .buffy/javascript.toml
[javascript.git]
name = "@example/tomato"
remote = "git@github.com:example/tomato-js.git"
branch = "main"
repository = "https://github.com/example/tomato-js"
grpc = true
keep = ["README.md"]
Fields
The same fields as npm (except registry and access), plus:
remote— Git URL the package is pushed to.branch— Branch to push to.keep— Files to preserve from the remote.
The remote field
remote = "git@github.com:example/tomato-js.git"
The branch field
branch = "main"
The keep field
keep = ["README.md"]
Default: [].
Example consumer usage
npm packages can be installed directly from a Git source:
npm install git+ssh://git@github.com/example/tomato-js.git#v0.1.0
TypeScript Profiles
The typescript profile generates a TypeScript library from your .proto
files using ts-proto and compiles it with tsc before publishing. The
package contains both compiled JavaScript and .d.ts declarations.
Available variants:
npm— Publish to npm or another npm-compatible registry.git— Push the generated package to a Git remote.
Required tools
protoc— Protocol Buffers compiler.protoc-gen-ts_proto— TypeScript generator fromts-proto.node— Node.js runtime.npm— npm CLI, used for install, build, and publish.tsc— TypeScript compiler.git— Required only for thegitvariant.
Install the TypeScript tooling globally:
npm install -g ts-proto typescript
The npm variant
Generates a TypeScript package under target/<profile>/, runs npm install
to fetch dependencies, runs tsc via npm run build to produce dist/, then
publishes via npm publish.
Example
# .buffy/ts-example.toml
[typescript.npm]
name = "@example/tomato"
registry = "https://registry.npmjs.org/"
access = "public"
repository = "https://github.com/example/tomato"
grpc = true
Fields
name— npm package name.registry— Registry URL.access—"public"or"restricted"for scoped packages.repository— Repository URL embedded in the package.homepage— Override the global homepage.grpc— Generate gRPC service stubs.
The semantics of these fields are the same as in the JavaScript npm
variant. The differences in the generated
output are in the code itself, not the configuration:
- TypeScript output uses ES interfaces and types instead of JavaScript classes.
- gRPC stubs use
nice-grpc(Node.js and browser compatible) instead ofgrpc-web. - The package includes a
tscbuild step before publishing, so consumers receive precompiled JavaScript and.d.tsfiles.
Required environment variables
| Variable | Purpose |
|---|---|
NPM_TOKEN | Auth token for the configured registry |
Example consumer usage
npm install @example/tomato
import { HelloRequest } from "@example/tomato";
const req: HelloRequest = { name: "World" };
console.log(req.name);
The git variant
Generates the same TypeScript package as npm, but commits and tags it to a
Git repository instead of uploading to a registry.
Example
# .buffy/typescript.toml
[typescript.git]
name = "@example/tomato"
remote = "git@github.com:example/tomato-ts.git"
branch = "main"
repository = "https://github.com/example/tomato-ts"
grpc = true
keep = ["README.md"]
Fields
The same fields as npm (except registry and access), plus:
remote— Git URL the package is pushed to.branch— Branch to push to.keep— Files to preserve from the remote.
These fields behave identically to the same-named fields in JavaScript profiles.
Example consumer usage
npm install git+ssh://git@github.com/example/tomato-ts.git#v0.1.0
Python Profiles
The python profile generates a Python package from your .proto files using
the grpc_tools.protoc plugin (which is the official Python code generator,
distributed as part of grpcio-tools). The package is published either to
PyPI (or any PyPI-compatible registry) or to a Git repository.
Available variants:
pypi— Publish to PyPI or another PEP 503 registry.git— Push the generated package to a Git remote.
Required tools
protoc— Protocol Buffers compiler (used viagrpcio-tools).python3— Python 3.9 or newer.grpcio-tools— Python module providing the protoc-based code generator.build— PEP 517 build module, used to produce sdist and wheel.twine— PyPI upload tool (only for thepypivariant).git— For thegitvariant.
Install the Python tooling:
pip install grpcio-tools build twine
The pypi variant
Generates a Python package under target/<profile>/, runs python -m build
to produce sdist and wheel under dist/, then uploads them with twine.
Example
# .buffy/python.toml
[python.pypi]
name = "tomato-proto"
repository_url = "https://upload.pypi.org/legacy/"
repository = "https://github.com/example/tomato"
grpc = true
Fields
name— PyPI package name. Hyphens are converted to underscores for the importable module name.repository_url— The PyPI-compatible upload endpoint. Default is the public PyPI; usehttps://test.pypi.org/legacy/for testing.repository— URL to the source repository.homepage— Optional override of the global homepage.grpc— Whentrue, generates gRPC service stubs via--grpc_python_out=....protobuf_version— Optional pin for theprotobufruntime dependency.grpcio_version— Optional pin for thegrpcioruntime dependency.
Required environment variables
| Variable | Purpose |
|---|---|
PYPI_TOKEN | API token for the configured PyPI |
Tokens are created at https://pypi.org/manage/account/token/ (or the
equivalent path on a private registry). Include the pypi- prefix.
Example consumer usage
pip install tomato-proto
from tomato_proto import greeter_pb2
req = greeter_pb2.HelloRequest(name="World")
print(req.name)
The git variant
Generates the same package as pypi, but commits and tags it to a Git
repository.
Example
[python.git]
name = "tomato-proto"
remote = "git@github.com:example/tomato-py.git"
branch = "main"
repository = "https://github.com/example/tomato-py"
grpc = true
keep = ["README.md"]
Example consumer usage
pip install git+https://github.com/example/tomato-py@v0.1.0
Environment Variables
Buffy reads credentials and a few configuration values from environment
variables rather than from Buffy.toml or the profile files. This keeps
secrets out of source control and matches the conventions of the underlying
tooling (Cargo, Maven, npm).
For local development, Buffy auto-loads a .env file from the current
directory via dotenvy. For CI, set the variables through your provider’s
secret store (GitHub Actions secrets, GitLab CI variables, etc.).
Quoting in .env files
dotenvy follows shell-like rules. Values containing $, #, whitespace, or
other special characters should be wrapped in single quotes to disable
variable expansion and escaping:
GPG_PASSPHRASE='HD$qOdHYiG#jGUCpNJhzSSx5W'
NPM_TOKEN='npm_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
Double-quoted values allow $VAR expansion, which can silently corrupt
secrets that contain a $.
Variables by language
Java and Kotlin
Used by the maven_central variant; not required for the git variant.
| Variable | Required | Purpose |
|---|---|---|
MAVEN_USERNAME | yes | Sonatype Central Portal username token |
MAVEN_PASSWORD | yes | Sonatype Central Portal password token |
GPG_KEY_ID | yes | GPG key ID used for signing artifacts |
GPG_PASSPHRASE | optional | Passphrase for the GPG key (omit if the key has no passphrase) |
GPG_PRIVATE_KEY | optional | Armored private key, imported on each run (intended for CI) |
The MAVEN_USERNAME / MAVEN_PASSWORD pair is generated as a token from your
account at central.sonatype.com under
Profile → User Token.
GPG_PRIVATE_KEY is intended for CI runners that don’t have a persistent
keyring. When set, Buffy writes the key to a temporary file, runs
gpg --import, and removes the file. Do not set this on machines that
already have the key in their local keyring.
Rust
Used by the crate variant; not required for the git variant.
| Variable | Required | Purpose |
|---|---|---|
CARGO_REGISTRY_TOKEN | yes | API token for the configured Cargo registry |
For crates.io, generate the token under Account → API Tokens. For
self-hosted registries (e.g. Kellnr), use the token-issuing mechanism of the
registry. The registry index URL must additionally be configured in
~/.cargo/config.toml:
[registries]
my-registry = { index = "sparse+https://my-kellnr.example.com/api/v1/crates/" }
JavaScript and TypeScript
Used by the npm variant; not required for the git variant.
| Variable | Required | Purpose |
|---|---|---|
NPM_TOKEN | yes | Auth token for the configured registry |
For the public npm registry, generate the token via npm token create or in
the npmjs.com web UI. For other registries (Verdaccio, GitHub Packages, etc.),
use the registry’s token-issuing mechanism.
Buffy writes a temporary .npmrc to the build directory containing the token
and the configured registry URL, then removes it after publishing. Existing
.npmrc files in your home directory are not used.
Golang
The golang profile only supports a git variant, which uses Git itself for
authentication. Configure your SSH agent or HTTPS credentials via Git as
usual; Buffy does not read any environment variables for this profile.
Variables by use case
Local development
The minimal .env to publish from a developer machine across all targets:
# Java / Kotlin (Maven Central)
MAVEN_USERNAME='your-sonatype-username'
MAVEN_PASSWORD='your-sonatype-token'
GPG_KEY_ID='YOURGPGKEYID0123456789ABCDEF'
GPG_PASSPHRASE='your-gpg-passphrase'
# Rust (crates.io or other Cargo registry)
CARGO_REGISTRY_TOKEN='your-cargo-token'
# JavaScript / TypeScript (npm)
NPM_TOKEN='your-npm-token'
For Git-only profiles, no environment variables are required — Git uses your SSH agent.
CI
In CI, additionally set GPG_PRIVATE_KEY so that the runner can import the
signing key on each run:
GPG_PRIVATE_KEY='-----BEGIN PGP PRIVATE KEY BLOCK-----
...
-----END PGP PRIVATE KEY BLOCK-----'
CI secret stores typically support multiline values directly. If yours does
not, base64-encode the key and decode it in your pipeline before exposing it
as GPG_PRIVATE_KEY.
Behavior
When a required variable is missing or set to an empty string, Buffy emits a diagnostic identifying the variable, the profile that requires it, and a short hint on how to obtain it. The build does not proceed past this point.
Variables are only consulted at the moment they are needed. For instance,
buffy check does not require any of these to be set, since it does not
publish anything.
Command-Line Interface
The buffy CLI reads Buffy.toml and the profiles in .buffy/, builds all
configured language targets in parallel, and (with --publish) pushes them to
their respective registries or remotes.
Synopsis
buffy [OPTIONS] [COMMAND]
When invoked without a subcommand, Buffy builds every profile in .buffy/. If
the .buffy/ directory does not exist or contains no profiles, Buffy exits
with a notice and no error.
Commands
buffy— Build all configured profiles.buffy check— Verify that required tools are installed.
buffy
Build (and optionally publish) every profile.
buffy
Each profile is built independently and in parallel. If any profile fails, Buffy continues building the others and reports a combined diagnostic at the end, listing every profile that succeeded and every profile that failed with its underlying error.
With --publish, every profile that built successfully is then published to
its configured destination.
buffy check
Verify that all required external tools are installed and reachable on the
PATH for the configured profiles. No code is generated and nothing is built.
buffy check
The check is profile-aware: it only verifies the tools needed by the
configured profiles. For instance, a project with only a rust profile does
not require mvn to be installed.
If a tool is missing, Buffy emits a diagnostic with the tool name and an installation hint specific to common platforms. See the per-language profile chapter for the full list of tools each variant requires.
Options
-p, --publish
Publish every profile after a successful build.
buffy --publish
Each profile is published to the destination configured in its
.buffy/<name>.toml file:
cratefor Rust → the configured Cargo registry (defaulting to crates.io).npmfor JavaScript / TypeScript → the configured npm registry.maven_centralfor Java / Kotlin → the Sonatype Central Portal.gitfor any language → the configured Git remote, with av<version>tag.
If a required environment variable is missing, Buffy reports a diagnostic with the variable name and a description of how to obtain it. See Environment Variables.
--publish-version <VERSION>
Override the version from Buffy.toml for this run.
buffy --publish --publish-version 1.2.3
The value must be a valid SemVer version. This is intended for CI pipelines
that derive the release version from a Git tag rather than committing it into
Buffy.toml.
The override applies only to the current invocation; the file on disk is not modified.
--verbose
Print the full output (both stdout and stderr) of every external tool
Buffy invokes.
buffy --verbose
By default, Buffy streams tool output to your terminal as it arrives, prefixed
with the profile name and the program being run. With --verbose, the
unfiltered streams are shown, which is useful when debugging:
- Maven build failures, where the relevant
[ERROR]lines are mixed in with a long[INFO]log onstdout. - npm install issues that print resolution diagnostics on
stdout. - Cargo warnings about deprecated APIs.
Without --verbose, Buffy still surfaces the relevant error context when a
command fails.
-h, --help
Print a short help message.
-V, --version
Print the Buffy version.
Exit codes
0— All profiles built (and, with--publish, published) successfully.1— One or more profiles failed. The diagnostic at the end of the run lists every failure.
Examples
Build all profiles:
buffy
Verify the toolchain without building:
buffy check
Build and publish using the version from Buffy.toml:
buffy --publish
Override the version (typical CI usage):
buffy --publish --publish-version "${GITHUB_REF_NAME#v}"
Get verbose output for debugging:
buffy --publish --verbose
Frequently Asked Questions
How is Buffy different from buf?
buf is a feature-rich linter, formatter, and code generator that
orchestrates protoc plugins. Buffy is a build and publishing tool: it cares
less about lint rules and breaking-change detection, and more about turning a
set of .proto files into a publishable Go module, Cargo crate, npm package,
or Maven artifact in one command. The two tools are complementary! You can
run buf lint and buf breaking in CI alongside buffy --publish.
Why generate per-language packages instead of using a Buf Schema Registry?
The Buf Schema Registry is an excellent option if you control both the producer and the consumer side and want strong centralization. Buffy is for teams that need protocol buffer libraries to land in the native package ecosystem of each language; so consumers depend on a normal Cargo crate or Maven artifact and don’t have to learn anything new.
Can I add a new language target?
Not as configuration. Language targets are part of Buffy’s source code. The
existing targets share a common skeleton (a profile, a code generator, a
build step, a publish step), so adding one is straightforward. See the
existing modules under targets/ in the Buffy repository for templates.
Why does the --publish step push with --force?
Buffy treats the published artifact’s repository (or the registry version) as
a derived output: every release is rebuilt from scratch. Hand-edits inside a
generated repository would be overwritten on the next publish, so Buffy
force-pushes to make the destination an exact mirror of the generated content.
Files listed in the keep field of git profiles are preserved across
publishes.
A profile failed but the others succeeded - what happens?
Buffy continues building the remaining profiles and reports a combined
diagnostic at the end, listing every profile that succeeded and every profile
that failed with its underlying error. The successful profiles are not
rolled back; if the failure occurred during --publish, the artifacts that
made it out are already live.
Can I use Buffy with private registries?
Yes:
- Cargo — Configure the registry index in
~/.cargo/config.tomland setregistry = "<name>"in the profile. - npm — Set the
registryfield in the profile to your registry’s URL (e.g., a Verdaccio instance, GitHub Packages, or AWS CodeArtifact). - Maven — The
maven_centralvariant is hard-wired to the Sonatype Central Portal. For private Maven repositories (Nexus, Artifactory), use thegitvariant for now and consume from the resulting repository, or open an issue to discuss adding a genericmavenvariant.
Why doesn’t buffy check need network access?
buffy check only verifies that the local toolchain is installed. It does
not invoke protoc, query crates.io for versions, or talk to any registry.
This makes it suitable as a fast pre-commit hook or CI gate.
How do I pin dependency versions in generated artifacts?
Each language’s profile has optional fields for pinning runtime versions:
protobuf_version for Java/Kotlin, prost_version and tonic_version for
Rust, etc. When omitted, Buffy queries the language’s registry for the latest
release version.
What happens if Buffy.toml and a profile both define the same field?
Profiles override the manifest where they overlap. For example, the
package-level homepage is the default, but the JavaScript profile’s
homepage field can override it for the JavaScript package only. Most
profile fields don’t overlap with the manifest; they configure
language-specific concerns.
Glossary
artifact
The build output of a single profile - a Go module, Cargo crate, Maven JAR,
or npm package, produced under target/<profile-name>/. With --publish,
the artifact is the thing that gets uploaded to a registry or pushed to a
Git remote.
manifest
The Buffy.toml file at the root of a Buffy project. Contains the
language-independent metadata (package name, version, license, authors).
See The Manifest Format.
package
The unit of code Buffy operates on: a directory containing a Buffy.toml,
a directory of .proto files (the source), and zero or more profiles
under .buffy/. A package produces one artifact per profile.
profile
A language-and-variant configuration in the .buffy/ directory. Each profile
is a TOML file selecting one language (e.g., rust, java) and one publishing
variant (e.g., crate, maven_central, git). The filename without .toml
becomes the profile name. See Profiles Format.
profile name
The filename of a profile in .buffy/ without the .toml extension. Profile
names appear as the subdirectory under target/ and as the prefix in build
output. They must be unique within a project.
registry
A central host that distributes packages for a language. Examples:
crates.io for Rust, npm for JavaScript and TypeScript,
Maven Central for Java and Kotlin. Buffy publishes to a registry via the
language’s native publishing variant (crate, npm, maven_central).
source
The directory of .proto files configured under the [source] section of
Buffy.toml. Buffy walks this directory recursively and passes every
.proto file it finds to protoc. Defaults to src if the section is
omitted.
target
The Buffy term for one published output (a Cargo crate, a Maven artifact,
an npm package). Corresponds to one profile. Not to be confused with the
target/ directory, which holds the generated content for every target.
variant
The publishing destination for a profile, selected as the second part of the
nested table name [<language>.<variant>]. Each language defines which
variants it supports. For example, rust supports crate and git, while
golang supports only git. See the per-language profile chapters.