Repository: bufbuild/buf Branch: main Commit: 185bc027f9e5 Files: 2916 Total size: 11.6 MB Directory structure: gitextract_uewxy4kd/ ├── .bufstyle.yaml ├── .dockerignore ├── .envrc ├── .gitattributes ├── .github/ │ ├── CODEOWNERS │ ├── ISSUE_TEMPLATE/ │ │ ├── 1-bug-report.yml │ │ ├── 2-feature-request.yml │ │ └── 3-other.yml │ ├── dependabot.yml │ └── workflows/ │ ├── add-to-project.yaml │ ├── back-to-development.yaml │ ├── buf-binary-size.yaml │ ├── buf-ci.yaml │ ├── build-and-draft-release.yaml │ ├── ci.yaml │ ├── codeql.yaml │ ├── create-release-pr.yaml │ ├── docker-publish.yaml │ ├── emergency-review-bypass.yaml │ ├── make-upgrade.yaml │ ├── notify-approval-bypass.yaml │ ├── pr-title.yaml │ ├── previous.yaml │ ├── verify-changelog.yaml │ └── windows.yaml ├── .gitignore ├── .godoclint.yaml ├── .golangci.yml ├── .pre-commit-hooks.yaml ├── CHANGELOG.md ├── Dockerfile.buf ├── LICENSE ├── Makefile ├── README.md ├── buf.yaml ├── cmd/ │ ├── buf/ │ │ ├── buf.go │ │ ├── buf_test.go │ │ ├── buf_unix_test.go │ │ ├── imports_test.go │ │ ├── internal/ │ │ │ ├── command/ │ │ │ │ ├── alpha/ │ │ │ │ │ ├── protoc/ │ │ │ │ │ │ ├── const_unix.go │ │ │ │ │ │ ├── const_windows.go │ │ │ │ │ │ ├── errors.go │ │ │ │ │ │ ├── flags.go │ │ │ │ │ │ ├── flags_test.go │ │ │ │ │ │ ├── flags_unix.go │ │ │ │ │ │ ├── flags_windows.go │ │ │ │ │ │ ├── internal/ │ │ │ │ │ │ │ ├── protoc-gen-insertion-point-receiver/ │ │ │ │ │ │ │ │ └── main.go │ │ │ │ │ │ │ └── protoc-gen-insertion-point-writer/ │ │ │ │ │ │ │ └── main.go │ │ │ │ │ │ ├── plugin.go │ │ │ │ │ │ ├── protoc.go │ │ │ │ │ │ ├── protoc_test.go │ │ │ │ │ │ └── testdata/ │ │ │ │ │ │ ├── 1/ │ │ │ │ │ │ │ └── flags.txt │ │ │ │ │ │ ├── 2/ │ │ │ │ │ │ │ ├── flags1.txt │ │ │ │ │ │ │ └── flags2.txt │ │ │ │ │ │ ├── 3/ │ │ │ │ │ │ │ ├── flags1.txt │ │ │ │ │ │ │ └── flags2.txt │ │ │ │ │ │ ├── insertion/ │ │ │ │ │ │ │ └── test.proto │ │ │ │ │ │ └── overlap/ │ │ │ │ │ │ ├── a/ │ │ │ │ │ │ │ ├── 1.proto │ │ │ │ │ │ │ └── 1.txt │ │ │ │ │ │ └── b/ │ │ │ │ │ │ ├── 1.txt │ │ │ │ │ │ └── 2.proto │ │ │ │ │ └── registry/ │ │ │ │ │ └── token/ │ │ │ │ │ ├── tokendelete/ │ │ │ │ │ │ └── tokendelete.go │ │ │ │ │ ├── tokenget/ │ │ │ │ │ │ └── tokenget.go │ │ │ │ │ └── tokenlist/ │ │ │ │ │ └── tokenlist.go │ │ │ │ ├── beta/ │ │ │ │ │ ├── bufpluginv1/ │ │ │ │ │ │ └── bufpluginv1.go │ │ │ │ │ ├── bufpluginv1beta1/ │ │ │ │ │ │ └── bufpluginv1beta1.go │ │ │ │ │ ├── bufpluginv2/ │ │ │ │ │ │ └── bufpluginv2.go │ │ │ │ │ ├── internal/ │ │ │ │ │ │ └── internal.go │ │ │ │ │ ├── price/ │ │ │ │ │ │ └── price.go │ │ │ │ │ ├── registry/ │ │ │ │ │ │ ├── plugin/ │ │ │ │ │ │ │ ├── plugindelete/ │ │ │ │ │ │ │ │ └── plugindelete.go │ │ │ │ │ │ │ └── pluginpush/ │ │ │ │ │ │ │ └── pluginpush.go │ │ │ │ │ │ └── webhook/ │ │ │ │ │ │ ├── webhookcreate/ │ │ │ │ │ │ │ └── webhookcreate.go │ │ │ │ │ │ ├── webhookdelete/ │ │ │ │ │ │ │ └── webhookdelete.go │ │ │ │ │ │ └── webhooklist/ │ │ │ │ │ │ └── webhooklist.go │ │ │ │ │ └── studioagent/ │ │ │ │ │ └── studioagent.go │ │ │ │ ├── breaking/ │ │ │ │ │ ├── breaking.go │ │ │ │ │ ├── breaking_test.go │ │ │ │ │ └── testdata/ │ │ │ │ │ └── workspace_new_module/ │ │ │ │ │ ├── against/ │ │ │ │ │ │ ├── b/ │ │ │ │ │ │ │ └── b.proto │ │ │ │ │ │ ├── buf.yaml │ │ │ │ │ │ └── c/ │ │ │ │ │ │ └── c.proto │ │ │ │ │ └── head/ │ │ │ │ │ ├── a/ │ │ │ │ │ │ └── a.proto │ │ │ │ │ ├── b/ │ │ │ │ │ │ └── b.proto │ │ │ │ │ ├── buf.yaml │ │ │ │ │ └── c/ │ │ │ │ │ └── c.proto │ │ │ │ ├── build/ │ │ │ │ │ └── build.go │ │ │ │ ├── config/ │ │ │ │ │ ├── configinit/ │ │ │ │ │ │ └── configinit.go │ │ │ │ │ ├── configlsbreakingrules/ │ │ │ │ │ │ └── configlsbreakingrules.go │ │ │ │ │ ├── configlslintrules/ │ │ │ │ │ │ └── configlslintrules.go │ │ │ │ │ ├── configlsmodules/ │ │ │ │ │ │ └── configlsmodules.go │ │ │ │ │ ├── configmigrate/ │ │ │ │ │ │ ├── configmigrate.go │ │ │ │ │ │ ├── configmigrate_test.go │ │ │ │ │ │ └── testdata/ │ │ │ │ │ │ ├── defaultv1/ │ │ │ │ │ │ │ ├── input/ │ │ │ │ │ │ │ │ └── buf.yaml │ │ │ │ │ │ │ └── output/ │ │ │ │ │ │ │ └── buf.yaml │ │ │ │ │ │ ├── defaultv1beta1/ │ │ │ │ │ │ │ ├── input/ │ │ │ │ │ │ │ │ └── buf.yaml │ │ │ │ │ │ │ └── output/ │ │ │ │ │ │ │ └── buf.yaml │ │ │ │ │ │ └── unknown/ │ │ │ │ │ │ └── input/ │ │ │ │ │ │ └── buf.yaml │ │ │ │ │ └── internal/ │ │ │ │ │ └── internal.go │ │ │ │ ├── convert/ │ │ │ │ │ ├── convert.go │ │ │ │ │ ├── convert_test.go │ │ │ │ │ └── testdata/ │ │ │ │ │ └── convert/ │ │ │ │ │ ├── README.md │ │ │ │ │ ├── bin_json/ │ │ │ │ │ │ ├── api.proto │ │ │ │ │ │ ├── buf.proto │ │ │ │ │ │ ├── buf.yaml │ │ │ │ │ │ ├── duration.binpb │ │ │ │ │ │ ├── duration.json │ │ │ │ │ │ ├── duration.txtpb │ │ │ │ │ │ ├── duration.yaml │ │ │ │ │ │ ├── image.binpb │ │ │ │ │ │ ├── image.json │ │ │ │ │ │ ├── image.txtpb │ │ │ │ │ │ ├── image.yaml │ │ │ │ │ │ ├── payload.binpb │ │ │ │ │ │ ├── payload.json │ │ │ │ │ │ ├── payload.txtpb │ │ │ │ │ │ └── payload.yaml │ │ │ │ │ └── descriptor.plain.binpb │ │ │ │ ├── curl/ │ │ │ │ │ └── curl.go │ │ │ │ ├── dep/ │ │ │ │ │ ├── depgraph/ │ │ │ │ │ │ └── depgraph.go │ │ │ │ │ ├── depprune/ │ │ │ │ │ │ └── depprune.go │ │ │ │ │ ├── depupdate/ │ │ │ │ │ │ └── depupdate.go │ │ │ │ │ └── internal/ │ │ │ │ │ └── internal.go │ │ │ │ ├── export/ │ │ │ │ │ └── export.go │ │ │ │ ├── format/ │ │ │ │ │ └── format.go │ │ │ │ ├── generate/ │ │ │ │ │ ├── generate.go │ │ │ │ │ ├── generate_test.go │ │ │ │ │ ├── generate_unix_test.go │ │ │ │ │ ├── generate_windows_test.go │ │ │ │ │ ├── internal/ │ │ │ │ │ │ └── protoc-gen-top-level-type-names-yaml/ │ │ │ │ │ │ └── main.go │ │ │ │ │ └── testdata/ │ │ │ │ │ ├── duplicate_plugins/ │ │ │ │ │ │ ├── a.proto │ │ │ │ │ │ └── buf.gen.yaml │ │ │ │ │ ├── insertion/ │ │ │ │ │ │ └── test.proto │ │ │ │ │ ├── insertion_point/ │ │ │ │ │ │ └── test.txt │ │ │ │ │ ├── nested_insertion_point/ │ │ │ │ │ │ └── gen/ │ │ │ │ │ │ └── proto/ │ │ │ │ │ │ └── insertion/ │ │ │ │ │ │ └── test.txt │ │ │ │ │ ├── paths/ │ │ │ │ │ │ ├── a/ │ │ │ │ │ │ │ ├── v1/ │ │ │ │ │ │ │ │ └── a.proto │ │ │ │ │ │ │ ├── v2/ │ │ │ │ │ │ │ │ └── a.proto │ │ │ │ │ │ │ └── v3/ │ │ │ │ │ │ │ ├── a.proto │ │ │ │ │ │ │ └── foo/ │ │ │ │ │ │ │ ├── bar.proto │ │ │ │ │ │ │ └── foo.proto │ │ │ │ │ │ ├── b/ │ │ │ │ │ │ │ └── v1/ │ │ │ │ │ │ │ └── b.proto │ │ │ │ │ │ ├── buf.gen.yaml │ │ │ │ │ │ └── buf.yaml │ │ │ │ │ ├── protofileref/ │ │ │ │ │ │ ├── a/ │ │ │ │ │ │ │ └── v1/ │ │ │ │ │ │ │ ├── a.proto │ │ │ │ │ │ │ └── b.proto │ │ │ │ │ │ ├── buf.gen.yaml │ │ │ │ │ │ └── buf.yaml │ │ │ │ │ ├── simple/ │ │ │ │ │ │ ├── a/ │ │ │ │ │ │ │ └── v1/ │ │ │ │ │ │ │ └── a.proto │ │ │ │ │ │ ├── buf.gen.yaml │ │ │ │ │ │ └── buf.yaml │ │ │ │ │ ├── types/ │ │ │ │ │ │ ├── a/ │ │ │ │ │ │ │ └── v1/ │ │ │ │ │ │ │ └── a.proto │ │ │ │ │ │ ├── buf.gen.yaml │ │ │ │ │ │ └── buf.yaml │ │ │ │ │ ├── v2/ │ │ │ │ │ │ ├── duplicate_plugins/ │ │ │ │ │ │ │ ├── a.proto │ │ │ │ │ │ │ └── buf.gen.yaml │ │ │ │ │ │ ├── local_plugin/ │ │ │ │ │ │ │ ├── a/ │ │ │ │ │ │ │ │ └── v1/ │ │ │ │ │ │ │ │ └── a.proto │ │ │ │ │ │ │ ├── b/ │ │ │ │ │ │ │ │ └── v1/ │ │ │ │ │ │ │ │ └── b.proto │ │ │ │ │ │ │ ├── buf.basic.gen.yaml │ │ │ │ │ │ │ ├── buf.exclude.paths.gen.yaml │ │ │ │ │ │ │ ├── buf.paths.gen.yaml │ │ │ │ │ │ │ ├── buf.types.gen.yaml │ │ │ │ │ │ │ └── buf.yaml │ │ │ │ │ │ ├── simple/ │ │ │ │ │ │ │ ├── a/ │ │ │ │ │ │ │ │ └── v1/ │ │ │ │ │ │ │ │ └── a.proto │ │ │ │ │ │ │ ├── buf.gen.yaml │ │ │ │ │ │ │ └── buf.yaml │ │ │ │ │ │ └── types/ │ │ │ │ │ │ ├── a/ │ │ │ │ │ │ │ └── v1/ │ │ │ │ │ │ │ └── a.proto │ │ │ │ │ │ ├── b/ │ │ │ │ │ │ │ └── v1/ │ │ │ │ │ │ │ └── b.proto │ │ │ │ │ │ ├── buf.gen.invalid.yaml │ │ │ │ │ │ ├── buf.gen.yaml │ │ │ │ │ │ ├── buf.yaml │ │ │ │ │ │ └── pkg/ │ │ │ │ │ │ └── v1/ │ │ │ │ │ │ └── options.proto │ │ │ │ │ └── workspace/ │ │ │ │ │ ├── a/ │ │ │ │ │ │ ├── v1/ │ │ │ │ │ │ │ └── a.proto │ │ │ │ │ │ ├── v2/ │ │ │ │ │ │ │ └── a.proto │ │ │ │ │ │ └── v3/ │ │ │ │ │ │ ├── a.proto │ │ │ │ │ │ └── foo/ │ │ │ │ │ │ ├── bar.proto │ │ │ │ │ │ └── foo.proto │ │ │ │ │ ├── b/ │ │ │ │ │ │ └── v1/ │ │ │ │ │ │ ├── b.proto │ │ │ │ │ │ └── foo.proto │ │ │ │ │ ├── buf.gen.yaml │ │ │ │ │ └── buf.work.yaml │ │ │ │ ├── lint/ │ │ │ │ │ └── lint.go │ │ │ │ ├── lsfiles/ │ │ │ │ │ └── lsfiles.go │ │ │ │ ├── lsp/ │ │ │ │ │ └── lspserve/ │ │ │ │ │ └── lspserve.go │ │ │ │ ├── mod/ │ │ │ │ │ ├── internal/ │ │ │ │ │ │ └── internal.go │ │ │ │ │ ├── modlsbreakingrules/ │ │ │ │ │ │ └── modlsbreakingrules.go │ │ │ │ │ ├── modlslintrules/ │ │ │ │ │ │ └── modlslintrules.go │ │ │ │ │ └── modopen/ │ │ │ │ │ └── modopen.go │ │ │ │ ├── plugin/ │ │ │ │ │ ├── pluginprune/ │ │ │ │ │ │ └── pluginprune.go │ │ │ │ │ ├── pluginpush/ │ │ │ │ │ │ └── pluginpush.go │ │ │ │ │ └── pluginupdate/ │ │ │ │ │ └── pluginupdate.go │ │ │ │ ├── policy/ │ │ │ │ │ ├── policyprune/ │ │ │ │ │ │ └── policyprune.go │ │ │ │ │ ├── policypush/ │ │ │ │ │ │ └── policypush.go │ │ │ │ │ └── policyupdate/ │ │ │ │ │ └── policyupdate.go │ │ │ │ ├── push/ │ │ │ │ │ └── push.go │ │ │ │ ├── registry/ │ │ │ │ │ ├── module/ │ │ │ │ │ │ ├── modulecommit/ │ │ │ │ │ │ │ ├── modulecommitaddlabel/ │ │ │ │ │ │ │ │ └── modulecommitaddlabel.go │ │ │ │ │ │ │ ├── modulecommitinfo/ │ │ │ │ │ │ │ │ └── modulecommitinfo.go │ │ │ │ │ │ │ ├── modulecommitlist/ │ │ │ │ │ │ │ │ └── modulecommitlist.go │ │ │ │ │ │ │ └── modulecommitresolve/ │ │ │ │ │ │ │ └── modulecommitresolve.go │ │ │ │ │ │ ├── modulecreate/ │ │ │ │ │ │ │ └── modulecreate.go │ │ │ │ │ │ ├── moduledelete/ │ │ │ │ │ │ │ └── moduledelete.go │ │ │ │ │ │ ├── moduledeprecate/ │ │ │ │ │ │ │ └── moduledeprecate.go │ │ │ │ │ │ ├── moduleinfo/ │ │ │ │ │ │ │ └── moduleinfo.go │ │ │ │ │ │ ├── modulelabel/ │ │ │ │ │ │ │ ├── modulelabelarchive/ │ │ │ │ │ │ │ │ └── modulelabelarchive.go │ │ │ │ │ │ │ ├── modulelabelinfo/ │ │ │ │ │ │ │ │ └── modulelabelinfo.go │ │ │ │ │ │ │ ├── modulelabellist/ │ │ │ │ │ │ │ │ └── modulelabellist.go │ │ │ │ │ │ │ └── modulelabelunarchive/ │ │ │ │ │ │ │ └── modulelabelunarchive.go │ │ │ │ │ │ ├── modulesettings/ │ │ │ │ │ │ │ └── modulesettingsupdate/ │ │ │ │ │ │ │ └── modulesettingsupdate.go │ │ │ │ │ │ └── moduleundeprecate/ │ │ │ │ │ │ └── moduleundeprecate.go │ │ │ │ │ ├── organization/ │ │ │ │ │ │ ├── organizationcreate/ │ │ │ │ │ │ │ └── organizationcreate.go │ │ │ │ │ │ ├── organizationdelete/ │ │ │ │ │ │ │ └── organizationdelete.go │ │ │ │ │ │ ├── organizationinfo/ │ │ │ │ │ │ │ └── organizationinfo.go │ │ │ │ │ │ └── organizationupdate/ │ │ │ │ │ │ └── organizationupdate.go │ │ │ │ │ ├── plugin/ │ │ │ │ │ │ ├── plugincommit/ │ │ │ │ │ │ │ ├── plugincommitaddlabel/ │ │ │ │ │ │ │ │ └── plugincommitaddlabel.go │ │ │ │ │ │ │ ├── plugincommitinfo/ │ │ │ │ │ │ │ │ └── plugincommitinfo.go │ │ │ │ │ │ │ ├── plugincommitlist/ │ │ │ │ │ │ │ │ └── plugincommitlist.go │ │ │ │ │ │ │ └── plugincommitresolve/ │ │ │ │ │ │ │ └── plugincommitresolve.go │ │ │ │ │ │ ├── plugincreate/ │ │ │ │ │ │ │ └── plugincreate.go │ │ │ │ │ │ ├── plugindelete/ │ │ │ │ │ │ │ └── plugindelete.go │ │ │ │ │ │ ├── plugininfo/ │ │ │ │ │ │ │ └── plugininfo.go │ │ │ │ │ │ ├── pluginlabel/ │ │ │ │ │ │ │ ├── pluginlabelarchive/ │ │ │ │ │ │ │ │ └── pluginlabelarchive.go │ │ │ │ │ │ │ ├── pluginlabelinfo/ │ │ │ │ │ │ │ │ └── pluginlabelinfo.go │ │ │ │ │ │ │ ├── pluginlabellist/ │ │ │ │ │ │ │ │ └── pluginlabellist.go │ │ │ │ │ │ │ └── pluginlabelunarchive/ │ │ │ │ │ │ │ └── pluginlabelunarchive.go │ │ │ │ │ │ └── pluginsettings/ │ │ │ │ │ │ └── pluginsettingsupdate/ │ │ │ │ │ │ └── pluginsettingsupdate.go │ │ │ │ │ ├── policy/ │ │ │ │ │ │ ├── policycommit/ │ │ │ │ │ │ │ ├── policycommitaddlabel/ │ │ │ │ │ │ │ │ └── policycommitaddlabel.go │ │ │ │ │ │ │ ├── policycommitinfo/ │ │ │ │ │ │ │ │ └── policycommitinfo.go │ │ │ │ │ │ │ ├── policycommitlist/ │ │ │ │ │ │ │ │ └── policycommitlist.go │ │ │ │ │ │ │ └── policycommitresolve/ │ │ │ │ │ │ │ └── policycommitresolve.go │ │ │ │ │ │ ├── policycreate/ │ │ │ │ │ │ │ └── policycreate.go │ │ │ │ │ │ ├── policydelete/ │ │ │ │ │ │ │ └── policydelete.go │ │ │ │ │ │ ├── policyinfo/ │ │ │ │ │ │ │ └── policyinfo.go │ │ │ │ │ │ ├── policylabel/ │ │ │ │ │ │ │ ├── policylabelarchive/ │ │ │ │ │ │ │ │ └── policylabelarchive.go │ │ │ │ │ │ │ ├── policylabelinfo/ │ │ │ │ │ │ │ │ └── policylabelinfo.go │ │ │ │ │ │ │ ├── policylabellist/ │ │ │ │ │ │ │ │ └── policylabellist.go │ │ │ │ │ │ │ └── policylabelunarchive/ │ │ │ │ │ │ │ └── policylabelunarchive.go │ │ │ │ │ │ └── policysettings/ │ │ │ │ │ │ └── policysettingsupdate/ │ │ │ │ │ │ └── policysettingsupdate.go │ │ │ │ │ ├── registrycc/ │ │ │ │ │ │ └── registrycc.go │ │ │ │ │ ├── registrylogin/ │ │ │ │ │ │ ├── client.go │ │ │ │ │ │ ├── client_darwin.go │ │ │ │ │ │ └── registrylogin.go │ │ │ │ │ ├── registrylogout/ │ │ │ │ │ │ └── registrylogout.go │ │ │ │ │ ├── sdk/ │ │ │ │ │ │ ├── sdkinfo/ │ │ │ │ │ │ │ └── sdkinfo.go │ │ │ │ │ │ └── version/ │ │ │ │ │ │ └── version.go │ │ │ │ │ └── whoami/ │ │ │ │ │ └── whoami.go │ │ │ │ ├── source/ │ │ │ │ │ └── sourceedit/ │ │ │ │ │ └── sourceeditdeprecate/ │ │ │ │ │ └── sourceeditdeprecate.go │ │ │ │ └── stats/ │ │ │ │ └── stats.go │ │ │ └── internaltesting/ │ │ │ └── internaltesting.go │ │ ├── testdata/ │ │ │ ├── check_plugins/ │ │ │ │ ├── current/ │ │ │ │ │ ├── buf.yaml │ │ │ │ │ ├── proto/ │ │ │ │ │ │ ├── api/ │ │ │ │ │ │ │ └── v1/ │ │ │ │ │ │ │ └── service.proto │ │ │ │ │ │ └── common/ │ │ │ │ │ │ ├── v1/ │ │ │ │ │ │ │ ├── breaking.proto │ │ │ │ │ │ │ └── common.proto │ │ │ │ │ │ └── v1alpha1/ │ │ │ │ │ │ ├── breaking.proto │ │ │ │ │ │ └── messages.proto │ │ │ │ │ └── vendor/ │ │ │ │ │ └── protovalidate/ │ │ │ │ │ └── buf/ │ │ │ │ │ └── validate/ │ │ │ │ │ └── validate.proto │ │ │ │ └── previous/ │ │ │ │ ├── buf.yaml │ │ │ │ ├── proto/ │ │ │ │ │ ├── api/ │ │ │ │ │ │ └── v1/ │ │ │ │ │ │ └── service.proto │ │ │ │ │ └── common/ │ │ │ │ │ ├── v1/ │ │ │ │ │ │ ├── breaking.proto │ │ │ │ │ │ └── common.proto │ │ │ │ │ └── v1alpha1/ │ │ │ │ │ ├── breaking.proto │ │ │ │ │ └── messages.proto │ │ │ │ └── vendor/ │ │ │ │ └── protovalidate/ │ │ │ │ └── buf/ │ │ │ │ └── validate/ │ │ │ │ └── validate.proto │ │ │ ├── customoptions1/ │ │ │ │ └── a.proto │ │ │ ├── export/ │ │ │ │ ├── another/ │ │ │ │ │ └── proto/ │ │ │ │ │ ├── README.md │ │ │ │ │ ├── another.proto │ │ │ │ │ └── buf.yaml │ │ │ │ ├── buf.work.yaml │ │ │ │ ├── other/ │ │ │ │ │ └── proto/ │ │ │ │ │ ├── LICENSE │ │ │ │ │ ├── buf.yaml │ │ │ │ │ ├── request.proto │ │ │ │ │ └── unimported.proto │ │ │ │ └── proto/ │ │ │ │ ├── LICENSE │ │ │ │ ├── README.md │ │ │ │ ├── buf.yaml │ │ │ │ └── rpc.proto │ │ │ ├── fail/ │ │ │ │ ├── buf/ │ │ │ │ │ └── buf.proto │ │ │ │ └── buf.yaml │ │ │ ├── fail2/ │ │ │ │ ├── buf/ │ │ │ │ │ ├── buf.proto │ │ │ │ │ ├── buf2.proto │ │ │ │ │ └── buf3.proto │ │ │ │ └── buf.yaml │ │ │ ├── fail_buf_mod/ │ │ │ │ ├── buf/ │ │ │ │ │ └── buf.proto │ │ │ │ └── buf.mod │ │ │ ├── failarchive/ │ │ │ │ └── fail/ │ │ │ │ ├── buf/ │ │ │ │ │ └── buf.proto │ │ │ │ └── buf.yaml │ │ │ ├── format/ │ │ │ │ ├── complex/ │ │ │ │ │ └── complex.proto │ │ │ │ ├── diff/ │ │ │ │ │ └── diff.proto │ │ │ │ ├── invalid/ │ │ │ │ │ └── invalid.proto │ │ │ │ └── simple/ │ │ │ │ └── simple.proto │ │ │ ├── imports/ │ │ │ │ ├── cache/ │ │ │ │ │ ├── README.md │ │ │ │ │ └── v3/ │ │ │ │ │ └── modules/ │ │ │ │ │ └── b5/ │ │ │ │ │ └── bufbuild.test/ │ │ │ │ │ └── bufbot/ │ │ │ │ │ ├── people/ │ │ │ │ │ │ └── fc7d540124fd42db92511c19a60a1d98/ │ │ │ │ │ │ ├── files/ │ │ │ │ │ │ │ └── people/ │ │ │ │ │ │ │ └── v1/ │ │ │ │ │ │ │ ├── people1.proto │ │ │ │ │ │ │ └── people2.proto │ │ │ │ │ │ ├── module.yaml │ │ │ │ │ │ └── v1_buf_yaml/ │ │ │ │ │ │ └── buf.yaml │ │ │ │ │ ├── school/ │ │ │ │ │ │ └── f4329b95720e46da93b5b4011a42ae7b/ │ │ │ │ │ │ ├── files/ │ │ │ │ │ │ │ └── school/ │ │ │ │ │ │ │ └── v1/ │ │ │ │ │ │ │ ├── school1.proto │ │ │ │ │ │ │ └── school2.proto │ │ │ │ │ │ ├── module.yaml │ │ │ │ │ │ └── v1_buf_yaml/ │ │ │ │ │ │ └── buf.yaml │ │ │ │ │ └── students/ │ │ │ │ │ └── 6c776ed5bee54462b06d31fb7f7c16b8/ │ │ │ │ │ ├── files/ │ │ │ │ │ │ └── students/ │ │ │ │ │ │ └── v1/ │ │ │ │ │ │ └── students.proto │ │ │ │ │ ├── module.yaml │ │ │ │ │ └── v1_buf_yaml/ │ │ │ │ │ └── buf.yaml │ │ │ │ ├── corrupted_cache_dep/ │ │ │ │ │ ├── README.md │ │ │ │ │ └── v3/ │ │ │ │ │ └── modules/ │ │ │ │ │ └── b5/ │ │ │ │ │ └── bufbuild.test/ │ │ │ │ │ └── bufbot/ │ │ │ │ │ ├── people/ │ │ │ │ │ │ └── fc7d540124fd42db92511c19a60a1d98/ │ │ │ │ │ │ ├── files/ │ │ │ │ │ │ │ └── people/ │ │ │ │ │ │ │ └── v1/ │ │ │ │ │ │ │ ├── people1.proto │ │ │ │ │ │ │ └── people2.proto │ │ │ │ │ │ ├── module.yaml │ │ │ │ │ │ └── v1_buf_yaml/ │ │ │ │ │ │ └── buf.yaml │ │ │ │ │ └── students/ │ │ │ │ │ └── 6c776ed5bee54462b06d31fb7f7c16b8/ │ │ │ │ │ ├── files/ │ │ │ │ │ │ └── students/ │ │ │ │ │ │ └── v1/ │ │ │ │ │ │ └── students.proto │ │ │ │ │ ├── module.yaml │ │ │ │ │ └── v1_buf_yaml/ │ │ │ │ │ └── buf.yaml │ │ │ │ ├── corrupted_cache_file/ │ │ │ │ │ ├── README.md │ │ │ │ │ └── v3/ │ │ │ │ │ └── modules/ │ │ │ │ │ └── b5/ │ │ │ │ │ └── bufbuild.test/ │ │ │ │ │ └── bufbot/ │ │ │ │ │ └── people/ │ │ │ │ │ └── fc7d540124fd42db92511c19a60a1d98/ │ │ │ │ │ ├── files/ │ │ │ │ │ │ └── people/ │ │ │ │ │ │ └── v1/ │ │ │ │ │ │ ├── people1.proto │ │ │ │ │ │ └── people2.proto │ │ │ │ │ ├── module.yaml │ │ │ │ │ └── v1_buf_yaml/ │ │ │ │ │ └── buf.yaml │ │ │ │ ├── failure/ │ │ │ │ │ ├── people/ │ │ │ │ │ │ ├── buf.yaml │ │ │ │ │ │ └── people/ │ │ │ │ │ │ └── v1/ │ │ │ │ │ │ └── people1.proto │ │ │ │ │ ├── school/ │ │ │ │ │ │ ├── buf.yaml │ │ │ │ │ │ └── school/ │ │ │ │ │ │ └── v1/ │ │ │ │ │ │ ├── school1.proto │ │ │ │ │ │ └── school2.proto │ │ │ │ │ ├── students/ │ │ │ │ │ │ ├── buf.yaml │ │ │ │ │ │ └── students/ │ │ │ │ │ │ └── v1/ │ │ │ │ │ │ └── students.proto │ │ │ │ │ └── workspace/ │ │ │ │ │ └── transitive_imports/ │ │ │ │ │ ├── a/ │ │ │ │ │ │ ├── a.proto │ │ │ │ │ │ └── buf.yaml │ │ │ │ │ ├── b/ │ │ │ │ │ │ ├── b.proto │ │ │ │ │ │ └── buf.yaml │ │ │ │ │ ├── buf.work.yaml │ │ │ │ │ └── c/ │ │ │ │ │ ├── buf.yaml │ │ │ │ │ └── c.proto │ │ │ │ └── success/ │ │ │ │ ├── people/ │ │ │ │ │ ├── buf.yaml │ │ │ │ │ └── people/ │ │ │ │ │ └── v1/ │ │ │ │ │ ├── people1.proto │ │ │ │ │ └── people2.proto │ │ │ │ ├── school/ │ │ │ │ │ ├── buf.yaml │ │ │ │ │ └── school/ │ │ │ │ │ └── v1/ │ │ │ │ │ ├── school1.proto │ │ │ │ │ └── school2.proto │ │ │ │ ├── students/ │ │ │ │ │ ├── buf.yaml │ │ │ │ │ └── students/ │ │ │ │ │ └── v1/ │ │ │ │ │ └── students.proto │ │ │ │ ├── wkt/ │ │ │ │ │ └── a.proto │ │ │ │ └── workspace/ │ │ │ │ ├── unnamed_local_only_modules/ │ │ │ │ │ ├── a/ │ │ │ │ │ │ └── a.proto │ │ │ │ │ ├── b/ │ │ │ │ │ │ └── b.proto │ │ │ │ │ ├── buf.work.yaml │ │ │ │ │ └── c/ │ │ │ │ │ └── c.proto │ │ │ │ └── valid_explicit_deps/ │ │ │ │ ├── buf.work.yaml │ │ │ │ ├── mod-a/ │ │ │ │ │ ├── a/ │ │ │ │ │ │ └── v1/ │ │ │ │ │ │ └── a.proto │ │ │ │ │ └── buf.yaml │ │ │ │ └── mod-b/ │ │ │ │ ├── b/ │ │ │ │ │ └── v1/ │ │ │ │ │ └── b.proto │ │ │ │ └── buf.yaml │ │ │ ├── lint_ignore_disabled/ │ │ │ │ ├── buf.yaml │ │ │ │ ├── proto/ │ │ │ │ │ └── a.proto │ │ │ │ └── vendor/ │ │ │ │ └── b.proto │ │ │ ├── lsmodules/ │ │ │ │ ├── extraconfigv1/ │ │ │ │ │ ├── buf.work.yaml │ │ │ │ │ ├── buf.yaml │ │ │ │ │ └── proto/ │ │ │ │ │ └── a.proto │ │ │ │ ├── extraconfigv2/ │ │ │ │ │ ├── buf.work.yaml │ │ │ │ │ ├── buf.yaml │ │ │ │ │ └── proto/ │ │ │ │ │ └── a.proto │ │ │ │ ├── workspaceinvalid/ │ │ │ │ │ ├── buf.work.yaml │ │ │ │ │ └── proto/ │ │ │ │ │ └── buf.yaml │ │ │ │ ├── workspacev1/ │ │ │ │ │ ├── a_v1/ │ │ │ │ │ │ └── buf.yaml │ │ │ │ │ ├── b_no_name_v1/ │ │ │ │ │ │ └── buf.yaml │ │ │ │ │ ├── buf.work.yaml │ │ │ │ │ ├── c_v1beta1/ │ │ │ │ │ │ └── buf.yaml │ │ │ │ │ ├── f_no_name_v1beta1/ │ │ │ │ │ │ └── buf.yaml │ │ │ │ │ └── not_pointed_by_workspace/ │ │ │ │ │ └── buf.yaml │ │ │ │ └── workspacev2/ │ │ │ │ └── buf.yaml │ │ │ ├── paths/ │ │ │ │ ├── a/ │ │ │ │ │ ├── v1/ │ │ │ │ │ │ └── a.proto │ │ │ │ │ ├── v2/ │ │ │ │ │ │ └── a.proto │ │ │ │ │ └── v3/ │ │ │ │ │ ├── a.proto │ │ │ │ │ └── foo/ │ │ │ │ │ ├── bar.proto │ │ │ │ │ └── foo.proto │ │ │ │ ├── b/ │ │ │ │ │ └── v1/ │ │ │ │ │ └── b.proto │ │ │ │ ├── buf.gen.yaml │ │ │ │ └── buf.yaml │ │ │ ├── policy_list_rules/ │ │ │ │ ├── buf.yaml │ │ │ │ ├── expected_ls_lint_rules_configured_only.txt │ │ │ │ └── policy.yaml │ │ │ ├── protofileref/ │ │ │ │ ├── breaking/ │ │ │ │ │ ├── a/ │ │ │ │ │ │ ├── bar.proto │ │ │ │ │ │ ├── buf.yaml │ │ │ │ │ │ └── foo.proto │ │ │ │ │ └── b/ │ │ │ │ │ ├── bar.proto │ │ │ │ │ ├── buf.yaml │ │ │ │ │ └── foo.proto │ │ │ │ ├── noworkspaceormodule/ │ │ │ │ │ ├── fail/ │ │ │ │ │ │ └── import.proto │ │ │ │ │ └── success/ │ │ │ │ │ └── simple.proto │ │ │ │ └── success/ │ │ │ │ ├── buf.proto │ │ │ │ ├── buf.yaml │ │ │ │ └── other.proto │ │ │ ├── small_list_rules/ │ │ │ │ └── buf.yaml │ │ │ ├── small_list_rules_yml/ │ │ │ │ └── config.yml │ │ │ ├── success/ │ │ │ │ ├── buf/ │ │ │ │ │ └── buf.proto │ │ │ │ └── buf.yaml │ │ │ ├── successnobufyaml/ │ │ │ │ └── buf/ │ │ │ │ └── buf.proto │ │ │ ├── symlinks/ │ │ │ │ └── a.proto │ │ │ ├── workspace/ │ │ │ │ ├── fail/ │ │ │ │ │ ├── absolute/ │ │ │ │ │ │ ├── buf.work.yaml │ │ │ │ │ │ └── windows/ │ │ │ │ │ │ └── buf.work.yaml │ │ │ │ │ ├── breaking/ │ │ │ │ │ │ ├── buf.work.yaml │ │ │ │ │ │ └── other/ │ │ │ │ │ │ └── proto/ │ │ │ │ │ │ ├── buf.yaml │ │ │ │ │ │ └── request.proto │ │ │ │ │ ├── diroverlap/ │ │ │ │ │ │ └── buf.work.yaml │ │ │ │ │ ├── duplicate/ │ │ │ │ │ │ ├── buf.work.yaml │ │ │ │ │ │ ├── other/ │ │ │ │ │ │ │ └── proto/ │ │ │ │ │ │ │ └── foo.proto │ │ │ │ │ │ └── proto/ │ │ │ │ │ │ └── foo.proto │ │ │ │ │ ├── invalidversion/ │ │ │ │ │ │ └── buf.work.yaml │ │ │ │ │ ├── jumpcontext/ │ │ │ │ │ │ └── buf.work.yaml │ │ │ │ │ ├── modulebreakingconfig/ │ │ │ │ │ │ ├── a/ │ │ │ │ │ │ │ └── proto/ │ │ │ │ │ │ │ ├── a/ │ │ │ │ │ │ │ │ └── v1/ │ │ │ │ │ │ │ │ └── a.proto │ │ │ │ │ │ │ └── buf.yaml │ │ │ │ │ │ ├── buf.work.yaml │ │ │ │ │ │ ├── other/ │ │ │ │ │ │ │ └── proto/ │ │ │ │ │ │ │ ├── buf.yaml │ │ │ │ │ │ │ └── request.proto │ │ │ │ │ │ └── proto/ │ │ │ │ │ │ ├── buf.yaml │ │ │ │ │ │ └── rpc.proto │ │ │ │ │ ├── nodirectories/ │ │ │ │ │ │ └── buf.work.yaml │ │ │ │ │ ├── notexist/ │ │ │ │ │ │ └── buf.work.yaml │ │ │ │ │ ├── noversion/ │ │ │ │ │ │ └── buf.work.yaml │ │ │ │ │ ├── overlap/ │ │ │ │ │ │ ├── buf.work.yaml │ │ │ │ │ │ └── proto/ │ │ │ │ │ │ └── buf/ │ │ │ │ │ │ └── buf.proto │ │ │ │ │ ├── symlink/ │ │ │ │ │ │ ├── a/ │ │ │ │ │ │ │ └── a.proto │ │ │ │ │ │ └── buf.work.yaml │ │ │ │ │ └── v2/ │ │ │ │ │ ├── absolute/ │ │ │ │ │ │ ├── buf.yaml │ │ │ │ │ │ └── windows/ │ │ │ │ │ │ └── buf.yaml │ │ │ │ │ ├── duplicate/ │ │ │ │ │ │ ├── buf.yaml │ │ │ │ │ │ ├── other/ │ │ │ │ │ │ │ └── proto/ │ │ │ │ │ │ │ └── v1/ │ │ │ │ │ │ │ ├── foo.proto │ │ │ │ │ │ │ └── inner/ │ │ │ │ │ │ │ └── bar.proto │ │ │ │ │ │ └── proto/ │ │ │ │ │ │ └── v1/ │ │ │ │ │ │ ├── foo.proto │ │ │ │ │ │ └── inner/ │ │ │ │ │ │ └── bar.proto │ │ │ │ │ ├── jumpcontext/ │ │ │ │ │ │ └── buf.yaml │ │ │ │ │ ├── modulebreakingconfig/ │ │ │ │ │ │ ├── a/ │ │ │ │ │ │ │ └── proto/ │ │ │ │ │ │ │ └── a/ │ │ │ │ │ │ │ └── v1/ │ │ │ │ │ │ │ └── a.proto │ │ │ │ │ │ ├── buf.yaml │ │ │ │ │ │ ├── other/ │ │ │ │ │ │ │ └── proto/ │ │ │ │ │ │ │ └── request.proto │ │ │ │ │ │ └── proto/ │ │ │ │ │ │ └── rpc.proto │ │ │ │ │ ├── notexist/ │ │ │ │ │ │ └── buf.yaml │ │ │ │ │ ├── overlap/ │ │ │ │ │ │ ├── buf.yaml │ │ │ │ │ │ └── proto/ │ │ │ │ │ │ └── buf/ │ │ │ │ │ │ └── buf.proto │ │ │ │ │ └── symlink/ │ │ │ │ │ ├── a/ │ │ │ │ │ │ └── a.proto │ │ │ │ │ └── buf.yaml │ │ │ │ └── success/ │ │ │ │ ├── breaking/ │ │ │ │ │ ├── a/ │ │ │ │ │ │ └── proto/ │ │ │ │ │ │ ├── a/ │ │ │ │ │ │ │ └── v1/ │ │ │ │ │ │ │ └── a.proto │ │ │ │ │ │ └── buf.yaml │ │ │ │ │ ├── buf.work.yaml │ │ │ │ │ ├── other/ │ │ │ │ │ │ └── proto/ │ │ │ │ │ │ ├── buf.yaml │ │ │ │ │ │ └── request.proto │ │ │ │ │ └── proto/ │ │ │ │ │ ├── buf.yaml │ │ │ │ │ └── rpc.proto │ │ │ │ ├── detached/ │ │ │ │ │ ├── buf.work.yaml │ │ │ │ │ ├── other/ │ │ │ │ │ │ └── proto/ │ │ │ │ │ │ └── request.proto │ │ │ │ │ └── proto/ │ │ │ │ │ └── rpc.proto │ │ │ │ ├── diamond/ │ │ │ │ │ ├── buf.work.yaml │ │ │ │ │ ├── other/ │ │ │ │ │ │ └── proto/ │ │ │ │ │ │ ├── buf.yaml │ │ │ │ │ │ └── c.proto │ │ │ │ │ ├── private/ │ │ │ │ │ │ └── proto/ │ │ │ │ │ │ ├── b.proto │ │ │ │ │ │ └── buf.yaml │ │ │ │ │ └── proto/ │ │ │ │ │ ├── a.proto │ │ │ │ │ └── buf.yaml │ │ │ │ ├── dir/ │ │ │ │ │ ├── a/ │ │ │ │ │ │ └── proto/ │ │ │ │ │ │ ├── a/ │ │ │ │ │ │ │ └── v1/ │ │ │ │ │ │ │ └── a.proto │ │ │ │ │ │ └── buf.yaml │ │ │ │ │ ├── buf.work.yaml │ │ │ │ │ ├── other/ │ │ │ │ │ │ └── proto/ │ │ │ │ │ │ ├── buf.yaml │ │ │ │ │ │ └── request.proto │ │ │ │ │ └── proto/ │ │ │ │ │ ├── buf.yaml │ │ │ │ │ └── rpc.proto │ │ │ │ ├── dir_buf_work/ │ │ │ │ │ ├── a/ │ │ │ │ │ │ └── proto/ │ │ │ │ │ │ ├── a/ │ │ │ │ │ │ │ └── v1/ │ │ │ │ │ │ │ └── a.proto │ │ │ │ │ │ └── buf.yaml │ │ │ │ │ ├── buf.work │ │ │ │ │ ├── other/ │ │ │ │ │ │ └── proto/ │ │ │ │ │ │ ├── buf.yaml │ │ │ │ │ │ └── request.proto │ │ │ │ │ └── proto/ │ │ │ │ │ ├── buf.yaml │ │ │ │ │ └── rpc.proto │ │ │ │ ├── duplicate_dir_path/ │ │ │ │ │ ├── buf.yaml │ │ │ │ │ ├── proto/ │ │ │ │ │ │ ├── shared/ │ │ │ │ │ │ │ └── prefix/ │ │ │ │ │ │ │ ├── bar/ │ │ │ │ │ │ │ │ └── v1/ │ │ │ │ │ │ │ │ └── bar.proto │ │ │ │ │ │ │ └── foo/ │ │ │ │ │ │ │ └── v1/ │ │ │ │ │ │ │ └── foo.proto │ │ │ │ │ │ └── shared1/ │ │ │ │ │ │ └── prefix/ │ │ │ │ │ │ ├── x/ │ │ │ │ │ │ │ └── x.proto │ │ │ │ │ │ └── y/ │ │ │ │ │ │ └── y.proto │ │ │ │ │ └── separate/ │ │ │ │ │ └── v1/ │ │ │ │ │ └── separate.proto │ │ │ │ ├── duplicate_dir_path_overlapping_include/ │ │ │ │ │ ├── buf.yaml │ │ │ │ │ └── proto/ │ │ │ │ │ └── foo/ │ │ │ │ │ ├── bar/ │ │ │ │ │ │ ├── baz/ │ │ │ │ │ │ │ ├── v1/ │ │ │ │ │ │ │ │ └── baz.proto │ │ │ │ │ │ │ └── v2/ │ │ │ │ │ │ │ └── baz.proto │ │ │ │ │ │ ├── v1/ │ │ │ │ │ │ │ └── bar.proto │ │ │ │ │ │ └── v2/ │ │ │ │ │ │ └── bar.proto │ │ │ │ │ ├── v1/ │ │ │ │ │ │ └── foo.proto │ │ │ │ │ └── v2/ │ │ │ │ │ └── foo.proto │ │ │ │ ├── lock/ │ │ │ │ │ ├── a/ │ │ │ │ │ │ ├── a.proto │ │ │ │ │ │ └── buf.yaml │ │ │ │ │ ├── b/ │ │ │ │ │ │ ├── b.proto │ │ │ │ │ │ └── buf.yaml │ │ │ │ │ └── buf.work.yaml │ │ │ │ ├── nested/ │ │ │ │ │ └── proto/ │ │ │ │ │ ├── buf.work.yaml │ │ │ │ │ ├── external/ │ │ │ │ │ │ ├── buf.yaml │ │ │ │ │ │ └── external.proto │ │ │ │ │ └── internal/ │ │ │ │ │ ├── buf.yaml │ │ │ │ │ └── internal.proto │ │ │ │ ├── noconfig/ │ │ │ │ │ ├── buf.work.yaml │ │ │ │ │ ├── other/ │ │ │ │ │ │ └── proto/ │ │ │ │ │ │ └── request.proto │ │ │ │ │ └── proto/ │ │ │ │ │ └── rpc.proto │ │ │ │ ├── overlapping_dir_path/ │ │ │ │ │ ├── buf.yaml │ │ │ │ │ └── proto/ │ │ │ │ │ ├── bar/ │ │ │ │ │ │ ├── bar.proto │ │ │ │ │ │ └── internal/ │ │ │ │ │ │ └── bar_internal.proto │ │ │ │ │ └── foo/ │ │ │ │ │ ├── foo.proto │ │ │ │ │ └── internal/ │ │ │ │ │ └── foo_internal.proto │ │ │ │ ├── protofileref/ │ │ │ │ │ ├── another/ │ │ │ │ │ │ ├── bar/ │ │ │ │ │ │ │ └── bar.proto │ │ │ │ │ │ └── foo/ │ │ │ │ │ │ └── foo.proto │ │ │ │ │ ├── buf.work.yaml │ │ │ │ │ └── proto/ │ │ │ │ │ └── baz.proto │ │ │ │ ├── roots/ │ │ │ │ │ ├── buf.work.yaml │ │ │ │ │ ├── module1/ │ │ │ │ │ │ ├── a/ │ │ │ │ │ │ │ └── a.proto │ │ │ │ │ │ └── buf.yaml │ │ │ │ │ └── module2/ │ │ │ │ │ ├── buf.yaml │ │ │ │ │ ├── other.buf.yaml │ │ │ │ │ ├── root1/ │ │ │ │ │ │ └── b/ │ │ │ │ │ │ └── b.proto │ │ │ │ │ ├── root2/ │ │ │ │ │ │ └── c/ │ │ │ │ │ │ └── c.proto │ │ │ │ │ └── root3/ │ │ │ │ │ └── d/ │ │ │ │ │ └── d.proto │ │ │ │ ├── shared_parent_dir/ │ │ │ │ │ ├── buf.yaml │ │ │ │ │ ├── parent/ │ │ │ │ │ │ ├── foo/ │ │ │ │ │ │ │ └── foo.proto │ │ │ │ │ │ └── nextlayer/ │ │ │ │ │ │ ├── bar/ │ │ │ │ │ │ │ └── bar.proto │ │ │ │ │ │ └── baz/ │ │ │ │ │ │ └── baz.proto │ │ │ │ │ └── standalone/ │ │ │ │ │ ├── imported.proto │ │ │ │ │ └── standalone.proto │ │ │ │ ├── symlink/ │ │ │ │ │ ├── a/ │ │ │ │ │ │ └── a.proto │ │ │ │ │ ├── buf.work.yaml │ │ │ │ │ └── c/ │ │ │ │ │ └── c.proto │ │ │ │ ├── transitive/ │ │ │ │ │ ├── buf.work.yaml │ │ │ │ │ ├── other/ │ │ │ │ │ │ └── proto/ │ │ │ │ │ │ ├── buf.yaml │ │ │ │ │ │ └── c.proto │ │ │ │ │ ├── private/ │ │ │ │ │ │ └── proto/ │ │ │ │ │ │ ├── b.proto │ │ │ │ │ │ └── buf.yaml │ │ │ │ │ └── proto/ │ │ │ │ │ ├── a.proto │ │ │ │ │ └── buf.yaml │ │ │ │ ├── v2/ │ │ │ │ │ ├── breaking/ │ │ │ │ │ │ ├── a/ │ │ │ │ │ │ │ └── proto/ │ │ │ │ │ │ │ └── a/ │ │ │ │ │ │ │ └── v1/ │ │ │ │ │ │ │ └── a.proto │ │ │ │ │ │ ├── buf.yaml │ │ │ │ │ │ ├── other/ │ │ │ │ │ │ │ └── proto/ │ │ │ │ │ │ │ └── request.proto │ │ │ │ │ │ └── proto/ │ │ │ │ │ │ └── rpc.proto │ │ │ │ │ ├── detached/ │ │ │ │ │ │ ├── buf.yaml │ │ │ │ │ │ ├── other/ │ │ │ │ │ │ │ └── proto/ │ │ │ │ │ │ │ └── request.proto │ │ │ │ │ │ └── proto/ │ │ │ │ │ │ └── rpc.proto │ │ │ │ │ ├── diamond/ │ │ │ │ │ │ ├── buf.yaml │ │ │ │ │ │ ├── other/ │ │ │ │ │ │ │ └── proto/ │ │ │ │ │ │ │ └── c.proto │ │ │ │ │ │ ├── private/ │ │ │ │ │ │ │ └── proto/ │ │ │ │ │ │ │ └── b.proto │ │ │ │ │ │ └── proto/ │ │ │ │ │ │ ├── a.proto │ │ │ │ │ │ └── buf.yaml │ │ │ │ │ ├── dir/ │ │ │ │ │ │ ├── a/ │ │ │ │ │ │ │ └── proto/ │ │ │ │ │ │ │ └── a/ │ │ │ │ │ │ │ └── v1/ │ │ │ │ │ │ │ └── a.proto │ │ │ │ │ │ ├── buf.yaml │ │ │ │ │ │ ├── other/ │ │ │ │ │ │ │ └── proto/ │ │ │ │ │ │ │ └── request.proto │ │ │ │ │ │ └── proto/ │ │ │ │ │ │ └── rpc.proto │ │ │ │ │ ├── export/ │ │ │ │ │ │ ├── another/ │ │ │ │ │ │ │ └── proto/ │ │ │ │ │ │ │ ├── README.md │ │ │ │ │ │ │ └── another.proto │ │ │ │ │ │ ├── buf.yaml │ │ │ │ │ │ ├── other/ │ │ │ │ │ │ │ └── proto/ │ │ │ │ │ │ │ ├── LICENSE │ │ │ │ │ │ │ ├── request.proto │ │ │ │ │ │ │ └── unimported.proto │ │ │ │ │ │ └── proto/ │ │ │ │ │ │ ├── LICENSE │ │ │ │ │ │ ├── README.md │ │ │ │ │ │ └── rpc.proto │ │ │ │ │ ├── nested/ │ │ │ │ │ │ └── proto/ │ │ │ │ │ │ ├── buf.yaml │ │ │ │ │ │ ├── external/ │ │ │ │ │ │ │ └── external.proto │ │ │ │ │ │ └── internal/ │ │ │ │ │ │ └── internal.proto │ │ │ │ │ ├── symlink/ │ │ │ │ │ │ ├── a/ │ │ │ │ │ │ │ └── a.proto │ │ │ │ │ │ ├── buf.yaml │ │ │ │ │ │ └── c/ │ │ │ │ │ │ └── c.proto │ │ │ │ │ ├── transitive/ │ │ │ │ │ │ ├── buf.yaml │ │ │ │ │ │ ├── other/ │ │ │ │ │ │ │ └── proto/ │ │ │ │ │ │ │ └── c.proto │ │ │ │ │ │ ├── private/ │ │ │ │ │ │ │ └── proto/ │ │ │ │ │ │ │ └── b.proto │ │ │ │ │ │ └── proto/ │ │ │ │ │ │ └── a.proto │ │ │ │ │ └── wkt/ │ │ │ │ │ ├── buf.yaml │ │ │ │ │ ├── other/ │ │ │ │ │ │ └── proto/ │ │ │ │ │ │ └── c/ │ │ │ │ │ │ └── c.proto │ │ │ │ │ └── proto/ │ │ │ │ │ ├── a/ │ │ │ │ │ │ └── a.proto │ │ │ │ │ └── b/ │ │ │ │ │ └── b.proto │ │ │ │ └── wkt/ │ │ │ │ ├── buf.work.yaml │ │ │ │ ├── other/ │ │ │ │ │ └── proto/ │ │ │ │ │ └── c/ │ │ │ │ │ └── c.proto │ │ │ │ └── proto/ │ │ │ │ ├── a/ │ │ │ │ │ └── a.proto │ │ │ │ └── b/ │ │ │ │ └── b.proto │ │ │ └── workspace_subdir/ │ │ │ ├── buf.work.yaml │ │ │ └── other/ │ │ │ └── proto/ │ │ │ ├── one/ │ │ │ │ ├── a.proto │ │ │ │ └── b.proto │ │ │ └── two/ │ │ │ └── c.proto │ │ ├── workspace_subdir_test.go │ │ ├── workspace_test.go │ │ ├── workspace_unix_test.go │ │ └── workspace_windows_test.go │ ├── protoc-gen-buf-breaking/ │ │ └── breaking.go │ └── protoc-gen-buf-lint/ │ ├── lint.go │ ├── lint_test.go │ └── testdata/ │ ├── fail/ │ │ ├── buf/ │ │ │ ├── buf.proto │ │ │ └── buf_two.proto │ │ ├── something.yaml │ │ └── v2.yaml │ └── unused-imports/ │ └── buf/ │ └── v1/ │ ├── a.proto │ ├── b.proto │ ├── c.proto │ ├── d.proto │ ├── e.proto │ ├── enum_option.proto │ ├── enumvalue_option.proto │ ├── extrange_option.proto │ ├── f.proto │ ├── field_option.proto │ ├── file_option.proto │ ├── g.proto │ ├── method_option.proto │ ├── msg_option.proto │ ├── oneof_option.proto │ └── service_option.proto ├── etc/ │ ├── bandeps/ │ │ └── bandeps.yaml │ ├── template/ │ │ ├── buf.go-client.gen.yaml │ │ └── buf.go.gen.yaml │ └── windows/ │ └── test.bash ├── go.mod ├── go.sum ├── make/ │ ├── buf/ │ │ ├── all.mk │ │ ├── docker/ │ │ │ └── Dockerfile.release │ │ └── scripts/ │ │ ├── binarysize.bash │ │ ├── brew.sh │ │ ├── checkandupdateprecommithooks.bash │ │ ├── createreleasepr.bash │ │ ├── draftrelease.bash │ │ ├── gobacktodevelopment.bash │ │ ├── newtodos.bash │ │ ├── release.bash │ │ └── verifychangelog.bash │ └── go/ │ ├── base.mk │ ├── bootstrap.mk │ ├── buf.mk │ ├── dep_bandeps.mk │ ├── dep_buf.mk │ ├── dep_bufprivateusage.mk │ ├── dep_bufstyle.mk │ ├── dep_git_ls_files_unstaged.mk │ ├── dep_godoclint.mk │ ├── dep_golangci_lint.mk │ ├── dep_govulncheck.mk │ ├── dep_jq.mk │ ├── dep_license_header.mk │ ├── dep_minisign.mk │ ├── dep_protoc.mk │ ├── dep_protoc_gen_connect_go.mk │ ├── dep_protoc_gen_go.mk │ ├── dep_yq.mk │ ├── docker.mk │ ├── go.mk │ ├── license_header.mk │ └── scripts/ │ ├── checkcmdpackage.bash │ ├── checknodiffgenerated.bash │ ├── checknolintlint.bash │ ├── githubactionmakeupgrade.bash │ └── pushall.bash ├── private/ │ ├── README.md │ ├── buf/ │ │ ├── bufapp/ │ │ │ ├── bufapp.go │ │ │ ├── bufapp_test.go │ │ │ └── usage.gen.go │ │ ├── bufcli/ │ │ │ ├── buf_work_yaml_file.go │ │ │ ├── buf_yaml_file.go │ │ │ ├── bufcli.go │ │ │ ├── cache.go │ │ │ ├── config_ignore_yaml.go │ │ │ ├── connectclient_config.go │ │ │ ├── controller.go │ │ │ ├── env.go │ │ │ ├── errors.go │ │ │ ├── flag_args_test.go │ │ │ ├── flags_args.go │ │ │ ├── graph_provider.go │ │ │ ├── module_key_provider.go │ │ │ ├── module_owner.go │ │ │ ├── plugin_key_provider.go │ │ │ ├── policy_key_provider.go │ │ │ ├── prompt.go │ │ │ ├── protoc_plugin.go │ │ │ ├── rules.go │ │ │ ├── uploader.go │ │ │ └── usage.gen.go │ │ ├── bufconvert/ │ │ │ ├── bufconvert.go │ │ │ ├── bufconvert_test.go │ │ │ └── usage.gen.go │ │ ├── bufctl/ │ │ │ ├── bufctl.go │ │ │ ├── controller.go │ │ │ ├── image_with_config.go │ │ │ ├── option.go │ │ │ └── usage.gen.go │ │ ├── bufcurl/ │ │ │ ├── bufcurl.go │ │ │ ├── headers.go │ │ │ ├── invoker.go │ │ │ ├── invoker_test.go │ │ │ ├── io.go │ │ │ ├── reflection_resolver.go │ │ │ ├── resolver.go │ │ │ ├── testdata/ │ │ │ │ ├── test.proto │ │ │ │ └── testdata.txt │ │ │ ├── tls.go │ │ │ ├── usage.gen.go │ │ │ └── verbose_transport.go │ │ ├── buffetch/ │ │ │ ├── buffetch.go │ │ │ ├── buffetch_test.go │ │ │ ├── dir_ref.go │ │ │ ├── format.go │ │ │ ├── internal/ │ │ │ │ ├── archive_ref.go │ │ │ │ ├── dir_ref.go │ │ │ │ ├── errors.go │ │ │ │ ├── git_ref.go │ │ │ │ ├── internal.go │ │ │ │ ├── module_ref.go │ │ │ │ ├── proto_file_ref.go │ │ │ │ ├── proto_file_writer.go │ │ │ │ ├── read_bucket_closer.go │ │ │ │ ├── read_write_bucket.go │ │ │ │ ├── reader.go │ │ │ │ ├── reader_test.go │ │ │ │ ├── ref_parser.go │ │ │ │ ├── ref_parser_test.go │ │ │ │ ├── single_ref.go │ │ │ │ ├── testdata/ │ │ │ │ │ ├── bufyaml/ │ │ │ │ │ │ └── one/ │ │ │ │ │ │ └── two/ │ │ │ │ │ │ └── three/ │ │ │ │ │ │ ├── buf.work.yaml │ │ │ │ │ │ └── four/ │ │ │ │ │ │ └── five/ │ │ │ │ │ │ ├── buf.yaml │ │ │ │ │ │ └── proto/ │ │ │ │ │ │ └── foo.proto │ │ │ │ │ ├── direndsinproto.proto/ │ │ │ │ │ │ └── empty │ │ │ │ │ └── nobufyaml/ │ │ │ │ │ └── one/ │ │ │ │ │ └── two/ │ │ │ │ │ └── three/ │ │ │ │ │ ├── buf.work.yaml │ │ │ │ │ └── four/ │ │ │ │ │ └── five/ │ │ │ │ │ └── proto/ │ │ │ │ │ └── foo.proto │ │ │ │ ├── usage.gen.go │ │ │ │ ├── util.go │ │ │ │ └── writer.go │ │ │ ├── message_ref.go │ │ │ ├── module_ref.go │ │ │ ├── proto_file_ref.go │ │ │ ├── proto_file_writer.go │ │ │ ├── reader.go │ │ │ ├── ref_parser.go │ │ │ ├── ref_parser_test.go │ │ │ ├── ref_parser_unix_test.go │ │ │ ├── source_ref.go │ │ │ ├── usage.gen.go │ │ │ └── writer.go │ │ ├── bufformat/ │ │ │ ├── bufformat.go │ │ │ ├── deprecate.go │ │ │ ├── deprecate_test.go │ │ │ ├── formatter.go │ │ │ ├── formatter_test.go │ │ │ ├── testdata/ │ │ │ │ ├── customoptions/ │ │ │ │ │ ├── options.golden │ │ │ │ │ └── options.proto │ │ │ │ ├── deprecate/ │ │ │ │ │ ├── already_deprecated.golden │ │ │ │ │ ├── already_deprecated.proto │ │ │ │ │ ├── enum_value_deprecation.golden │ │ │ │ │ ├── enum_value_deprecation.proto │ │ │ │ │ ├── field_deprecation.golden │ │ │ │ │ ├── field_deprecation.proto │ │ │ │ │ ├── nested_types.golden │ │ │ │ │ └── nested_types.proto │ │ │ │ ├── editions/ │ │ │ │ │ ├── 2023/ │ │ │ │ │ │ ├── editions.golden │ │ │ │ │ │ └── editions.proto │ │ │ │ │ └── 2024/ │ │ │ │ │ └── editions.proto │ │ │ │ ├── header/ │ │ │ │ │ ├── nopackage.golden │ │ │ │ │ ├── nopackage.proto │ │ │ │ │ ├── nosyntax.golden │ │ │ │ │ └── nosyntax.proto │ │ │ │ ├── proto2/ │ │ │ │ │ ├── enum/ │ │ │ │ │ │ └── v1/ │ │ │ │ │ │ ├── empty.golden │ │ │ │ │ │ ├── empty.proto │ │ │ │ │ │ ├── enum.golden │ │ │ │ │ │ ├── enum.proto │ │ │ │ │ │ ├── enum_trailing_comment.golden │ │ │ │ │ │ ├── enum_trailing_comment.proto │ │ │ │ │ │ ├── enum_trailing_newline.golden │ │ │ │ │ │ ├── enum_trailing_newline.proto │ │ │ │ │ │ ├── enum_value_trailing_comment.golden │ │ │ │ │ │ └── enum_value_trailing_comment.proto │ │ │ │ │ ├── extend/ │ │ │ │ │ │ └── v1/ │ │ │ │ │ │ ├── empty.golden │ │ │ │ │ │ └── empty.proto │ │ │ │ │ ├── field/ │ │ │ │ │ │ └── v1/ │ │ │ │ │ │ ├── option.golden │ │ │ │ │ │ └── option.proto │ │ │ │ │ ├── group/ │ │ │ │ │ │ └── v1/ │ │ │ │ │ │ ├── empty.golden │ │ │ │ │ │ └── empty.proto │ │ │ │ │ ├── header/ │ │ │ │ │ │ └── v1/ │ │ │ │ │ │ ├── import.golden │ │ │ │ │ │ ├── import.proto │ │ │ │ │ │ ├── option.golden │ │ │ │ │ │ ├── option.proto │ │ │ │ │ │ ├── package.golden │ │ │ │ │ │ └── package.proto │ │ │ │ │ ├── license/ │ │ │ │ │ │ └── v1/ │ │ │ │ │ │ ├── enum.golden │ │ │ │ │ │ ├── enum.proto │ │ │ │ │ │ ├── message.golden │ │ │ │ │ │ └── message.proto │ │ │ │ │ ├── message/ │ │ │ │ │ │ └── v1/ │ │ │ │ │ │ ├── empty.golden │ │ │ │ │ │ ├── empty.proto │ │ │ │ │ │ ├── message.golden │ │ │ │ │ │ ├── message.proto │ │ │ │ │ │ ├── message_extensions.golden │ │ │ │ │ │ ├── message_extensions.proto │ │ │ │ │ │ ├── message_group.golden │ │ │ │ │ │ ├── message_group.proto │ │ │ │ │ │ ├── message_import.golden │ │ │ │ │ │ ├── message_import.proto │ │ │ │ │ │ ├── message_options.golden │ │ │ │ │ │ ├── message_options.proto │ │ │ │ │ │ ├── multiple_nested.golden │ │ │ │ │ │ ├── multiple_nested.proto │ │ │ │ │ │ ├── nested.golden │ │ │ │ │ │ ├── nested.proto │ │ │ │ │ │ ├── sparse.golden │ │ │ │ │ │ └── sparse.proto │ │ │ │ │ ├── option/ │ │ │ │ │ │ └── v1/ │ │ │ │ │ │ ├── option.golden │ │ │ │ │ │ ├── option.proto │ │ │ │ │ │ ├── option_array.golden │ │ │ │ │ │ ├── option_array.proto │ │ │ │ │ │ ├── option_complex_array_literal.golden │ │ │ │ │ │ ├── option_complex_array_literal.proto │ │ │ │ │ │ ├── option_compound_name.golden │ │ │ │ │ │ ├── option_compound_name.proto │ │ │ │ │ │ ├── option_message_field.golden │ │ │ │ │ │ ├── option_message_field.proto │ │ │ │ │ │ ├── option_name.golden │ │ │ │ │ │ ├── option_name.proto │ │ │ │ │ │ ├── single_compact_option.golden │ │ │ │ │ │ └── single_compact_option.proto │ │ │ │ │ ├── quotes/ │ │ │ │ │ │ └── v1/ │ │ │ │ │ │ ├── quotes.golden │ │ │ │ │ │ └── quotes.proto │ │ │ │ │ └── utf8/ │ │ │ │ │ └── v1/ │ │ │ │ │ ├── utf8.golden │ │ │ │ │ └── utf8.proto │ │ │ │ └── proto3/ │ │ │ │ ├── all/ │ │ │ │ │ └── v1/ │ │ │ │ │ ├── all.golden │ │ │ │ │ └── all.proto │ │ │ │ ├── block/ │ │ │ │ │ └── v1/ │ │ │ │ │ ├── block.golden │ │ │ │ │ └── block.proto │ │ │ │ ├── file/ │ │ │ │ │ └── v1/ │ │ │ │ │ ├── empty.golden │ │ │ │ │ ├── empty.proto │ │ │ │ │ ├── eof.golden │ │ │ │ │ ├── eof.proto │ │ │ │ │ ├── option.golden │ │ │ │ │ └── option.proto │ │ │ │ ├── header/ │ │ │ │ │ └── v1/ │ │ │ │ │ ├── header-block.golden │ │ │ │ │ ├── header-block.proto │ │ │ │ │ ├── header.golden │ │ │ │ │ ├── header.proto │ │ │ │ │ ├── unused_import.golden │ │ │ │ │ └── unused_import.proto │ │ │ │ ├── literal/ │ │ │ │ │ └── v1/ │ │ │ │ │ ├── compound_string.golden │ │ │ │ │ ├── compound_string.proto │ │ │ │ │ ├── literal.golden │ │ │ │ │ ├── literal.proto │ │ │ │ │ ├── literal_comments.golden │ │ │ │ │ ├── literal_comments.proto │ │ │ │ │ ├── special_literal.golden │ │ │ │ │ └── special_literal.proto │ │ │ │ ├── oneof/ │ │ │ │ │ └── v1/ │ │ │ │ │ ├── multiple.golden │ │ │ │ │ ├── multiple.proto │ │ │ │ │ ├── simple.golden │ │ │ │ │ └── simple.proto │ │ │ │ ├── range/ │ │ │ │ │ └── v1/ │ │ │ │ │ ├── range.golden │ │ │ │ │ └── range.proto │ │ │ │ └── service/ │ │ │ │ └── v1/ │ │ │ │ ├── empty.golden │ │ │ │ ├── empty.proto │ │ │ │ ├── empty_rpc.golden │ │ │ │ ├── empty_rpc.proto │ │ │ │ ├── service.golden │ │ │ │ ├── service.proto │ │ │ │ ├── service_options.golden │ │ │ │ ├── service_options.proto │ │ │ │ ├── simple.golden │ │ │ │ └── simple.proto │ │ │ └── usage.gen.go │ │ ├── bufgen/ │ │ │ ├── bufgen.go │ │ │ ├── features.go │ │ │ ├── features_test.go │ │ │ ├── generator.go │ │ │ ├── testdata/ │ │ │ │ ├── v1/ │ │ │ │ │ ├── buf.gen.yaml │ │ │ │ │ ├── gen_error1.yaml │ │ │ │ │ ├── gen_error10.yaml │ │ │ │ │ ├── gen_error11.yaml │ │ │ │ │ ├── gen_error12.yaml │ │ │ │ │ ├── gen_error13.yaml │ │ │ │ │ ├── gen_error14.yaml │ │ │ │ │ ├── gen_error2.yaml │ │ │ │ │ ├── gen_error3.yaml │ │ │ │ │ ├── gen_error4.yaml │ │ │ │ │ ├── gen_error5.yaml │ │ │ │ │ ├── gen_error6.yaml │ │ │ │ │ ├── gen_error7.yaml │ │ │ │ │ ├── gen_error8.yaml │ │ │ │ │ ├── gen_error9.yaml │ │ │ │ │ ├── gen_success1.json │ │ │ │ │ ├── gen_success1.yaml │ │ │ │ │ ├── gen_success2.json │ │ │ │ │ ├── gen_success2.yaml │ │ │ │ │ ├── gen_success3.json │ │ │ │ │ ├── gen_success3.yaml │ │ │ │ │ ├── gen_success3.yml │ │ │ │ │ ├── gen_success4.json │ │ │ │ │ ├── gen_success4.yaml │ │ │ │ │ ├── gen_success4.yml │ │ │ │ │ ├── gen_success5.json │ │ │ │ │ ├── gen_success5.yaml │ │ │ │ │ ├── gen_success5.yml │ │ │ │ │ ├── gen_success6.json │ │ │ │ │ ├── gen_success6.yaml │ │ │ │ │ ├── gen_success6.yml │ │ │ │ │ ├── gen_success7.json │ │ │ │ │ ├── gen_success7.yaml │ │ │ │ │ ├── gen_success7.yml │ │ │ │ │ ├── gen_success8.json │ │ │ │ │ ├── gen_success8.yaml │ │ │ │ │ ├── gen_success8.yml │ │ │ │ │ ├── gen_success9.json │ │ │ │ │ ├── gen_success9.yaml │ │ │ │ │ ├── gen_success9.yml │ │ │ │ │ ├── go_gen_error1.yaml │ │ │ │ │ ├── go_gen_error2.yaml │ │ │ │ │ ├── go_gen_error3.yaml │ │ │ │ │ ├── go_gen_error4.yaml │ │ │ │ │ ├── go_gen_error5.yaml │ │ │ │ │ ├── go_gen_error6.yaml │ │ │ │ │ ├── go_gen_success1.json │ │ │ │ │ └── go_gen_success1.yaml │ │ │ │ └── v1beta1/ │ │ │ │ ├── buf.gen.yaml │ │ │ │ ├── gen_error1.yaml │ │ │ │ ├── gen_error2.yaml │ │ │ │ ├── gen_error3.yaml │ │ │ │ ├── gen_success1.json │ │ │ │ ├── gen_success1.yaml │ │ │ │ ├── gen_success2.json │ │ │ │ ├── gen_success2.yaml │ │ │ │ ├── gen_success3.json │ │ │ │ ├── gen_success3.yaml │ │ │ │ ├── gen_success3.yml │ │ │ │ └── gen_success4_nopath.yaml │ │ │ └── usage.gen.go │ │ ├── buflsp/ │ │ │ ├── buflsp.go │ │ │ ├── buflsp_test.go │ │ │ ├── builtin.go │ │ │ ├── cel.go │ │ │ ├── completion.go │ │ │ ├── completion_cel.go │ │ │ ├── completion_cel_test.go │ │ │ ├── completion_test.go │ │ │ ├── definition_cel.go │ │ │ ├── definition_cel_test.go │ │ │ ├── definition_test.go │ │ │ ├── deprecate.go │ │ │ ├── deprecate_test.go │ │ │ ├── diagnostic.go │ │ │ ├── diagnostics_test.go │ │ │ ├── document_highlight_test.go │ │ │ ├── document_link.go │ │ │ ├── document_link_test.go │ │ │ ├── document_symbol_test.go │ │ │ ├── file.go │ │ │ ├── file_manager.go │ │ │ ├── folding_range.go │ │ │ ├── folding_range_test.go │ │ │ ├── format_test.go │ │ │ ├── hover_cel.go │ │ │ ├── hover_cel_test.go │ │ │ ├── hover_test.go │ │ │ ├── image.go │ │ │ ├── jsonrpc_wrappers.go │ │ │ ├── lint_ignore.go │ │ │ ├── lint_ignore_test.go │ │ │ ├── nyi.go │ │ │ ├── organize_imports.go │ │ │ ├── organize_imports_test.go │ │ │ ├── references_test.go │ │ │ ├── rename_test.go │ │ │ ├── semantic_tokens.go │ │ │ ├── semantic_tokens_cel.go │ │ │ ├── semantic_tokens_test.go │ │ │ ├── server.go │ │ │ ├── symbol.go │ │ │ ├── symbol_test.go │ │ │ ├── testdata/ │ │ │ │ ├── completion/ │ │ │ │ │ ├── buf.yaml │ │ │ │ │ ├── cel_completion.proto │ │ │ │ │ ├── extensions.proto │ │ │ │ │ ├── map_test.proto │ │ │ │ │ ├── options_test.proto │ │ │ │ │ ├── test.proto │ │ │ │ │ └── update_test.proto │ │ │ │ ├── definition/ │ │ │ │ │ ├── buf.yaml │ │ │ │ │ ├── cel_definition.proto │ │ │ │ │ ├── definition.proto │ │ │ │ │ └── types.proto │ │ │ │ ├── deprecate/ │ │ │ │ │ ├── edge_cases_test.proto │ │ │ │ │ ├── message_test.proto │ │ │ │ │ └── nested_test.proto │ │ │ │ ├── diagnostics/ │ │ │ │ │ ├── buf.yaml │ │ │ │ │ ├── syntax_error.proto │ │ │ │ │ ├── unresolved_import.proto │ │ │ │ │ ├── unused_import.proto │ │ │ │ │ └── valid.proto │ │ │ │ ├── document_highlight/ │ │ │ │ │ ├── buf.yaml │ │ │ │ │ ├── highlight.proto │ │ │ │ │ └── types.proto │ │ │ │ ├── document_link/ │ │ │ │ │ ├── buf.yaml │ │ │ │ │ ├── main.proto │ │ │ │ │ ├── types.proto │ │ │ │ │ └── wkt.proto │ │ │ │ ├── folding_range/ │ │ │ │ │ ├── buf.yaml │ │ │ │ │ ├── comments.proto │ │ │ │ │ ├── extensions.proto │ │ │ │ │ ├── folding.proto │ │ │ │ │ ├── imports.proto │ │ │ │ │ ├── minimal.proto │ │ │ │ │ ├── nested.proto │ │ │ │ │ └── options.proto │ │ │ │ ├── format/ │ │ │ │ │ ├── buf.yaml │ │ │ │ │ ├── formatted.proto │ │ │ │ │ └── unformatted.proto │ │ │ │ ├── hover/ │ │ │ │ │ ├── buf.yaml │ │ │ │ │ ├── cel_comprehensive.proto │ │ │ │ │ ├── test.proto │ │ │ │ │ ├── unused_import.proto │ │ │ │ │ └── unused_import_no_blank_line.proto │ │ │ │ ├── lint_ignore/ │ │ │ │ │ ├── already_ignored_test.proto │ │ │ │ │ ├── buf.yaml │ │ │ │ │ ├── different_rule_test.proto │ │ │ │ │ ├── field_test.proto │ │ │ │ │ ├── filewide_test.proto │ │ │ │ │ ├── multiple_test.proto │ │ │ │ │ ├── nested_test.proto │ │ │ │ │ ├── syntax_test.proto │ │ │ │ │ ├── tabs_test.proto │ │ │ │ │ └── trailing_comment_test.proto │ │ │ │ ├── organize_imports/ │ │ │ │ │ ├── buf.yaml │ │ │ │ │ ├── comments_test.proto │ │ │ │ │ ├── duplicate_test.proto │ │ │ │ │ ├── import_test.proto │ │ │ │ │ ├── modifier_test.proto │ │ │ │ │ ├── nochanges_test.proto │ │ │ │ │ ├── noimports_test.proto │ │ │ │ │ ├── nopackage_test.proto │ │ │ │ │ ├── options/ │ │ │ │ │ │ └── custom_option.proto │ │ │ │ │ ├── types/ │ │ │ │ │ │ ├── existing_field.proto │ │ │ │ │ │ ├── method_input.proto │ │ │ │ │ │ ├── method_output.proto │ │ │ │ │ │ ├── modifier_public.proto │ │ │ │ │ │ ├── modifier_weak.proto │ │ │ │ │ │ ├── nested_field.proto │ │ │ │ │ │ ├── notattop_import.proto │ │ │ │ │ │ └── toplevel_field.proto │ │ │ │ │ └── unknown_test.proto │ │ │ │ ├── references/ │ │ │ │ │ ├── buf.yaml │ │ │ │ │ ├── references.proto │ │ │ │ │ └── types.proto │ │ │ │ ├── rename/ │ │ │ │ │ ├── buf.yaml │ │ │ │ │ ├── extensions.proto │ │ │ │ │ ├── rename.proto │ │ │ │ │ ├── subpkg/ │ │ │ │ │ │ └── options.proto │ │ │ │ │ ├── types.proto │ │ │ │ │ └── wkt.proto │ │ │ │ ├── semantic_tokens/ │ │ │ │ │ ├── buf.yaml │ │ │ │ │ ├── cel.proto │ │ │ │ │ ├── cel_advanced.proto │ │ │ │ │ ├── cel_invalid.proto │ │ │ │ │ ├── cel_nested.proto │ │ │ │ │ ├── comprehensive.proto │ │ │ │ │ ├── edition.proto │ │ │ │ │ └── proto2.proto │ │ │ │ ├── symbols/ │ │ │ │ │ ├── buf.yaml │ │ │ │ │ └── symbols.proto │ │ │ │ ├── type_definition/ │ │ │ │ │ ├── buf.yaml │ │ │ │ │ ├── type_definition.proto │ │ │ │ │ └── types.proto │ │ │ │ ├── uri@encode/ │ │ │ │ │ ├── buf.yaml │ │ │ │ │ ├── test.proto │ │ │ │ │ └── types.proto │ │ │ │ ├── vendor/ │ │ │ │ │ └── protovalidate/ │ │ │ │ │ └── buf/ │ │ │ │ │ └── validate/ │ │ │ │ │ └── validate.proto │ │ │ │ └── workspace_symbols/ │ │ │ │ ├── buf.yaml │ │ │ │ ├── types.proto │ │ │ │ └── workspace_symbols.proto │ │ │ ├── type_definition_test.go │ │ │ ├── uri.go │ │ │ ├── uri_test.go │ │ │ ├── usage.gen.go │ │ │ ├── workspace.go │ │ │ └── workspace_symbol_test.go │ │ ├── bufmigrate/ │ │ │ ├── bufmigrate.go │ │ │ ├── migrate_builder.go │ │ │ ├── migrator.go │ │ │ └── usage.gen.go │ │ ├── bufprint/ │ │ │ ├── bufprint.go │ │ │ ├── curated_plugin_printer.go │ │ │ ├── pagination_wrapper.go │ │ │ ├── sdk_info_printer.go │ │ │ ├── stats_printer.go │ │ │ ├── tab_writer.go │ │ │ ├── token_json_printer.go │ │ │ ├── token_text_printer.go │ │ │ └── usage.gen.go │ │ ├── bufprotoc/ │ │ │ ├── bufprotoc.go │ │ │ ├── bufprotoc_test.go │ │ │ ├── testdata/ │ │ │ │ └── basic/ │ │ │ │ ├── bsr/ │ │ │ │ │ └── buf.testing/ │ │ │ │ │ └── acme/ │ │ │ │ │ ├── date/ │ │ │ │ │ │ └── acme/ │ │ │ │ │ │ └── date/ │ │ │ │ │ │ └── v1/ │ │ │ │ │ │ └── date.proto │ │ │ │ │ └── extension/ │ │ │ │ │ └── acme/ │ │ │ │ │ └── extension/ │ │ │ │ │ └── v1/ │ │ │ │ │ └── extension.proto │ │ │ │ └── workspacev1/ │ │ │ │ ├── common/ │ │ │ │ │ ├── geo/ │ │ │ │ │ │ └── proto/ │ │ │ │ │ │ └── acme/ │ │ │ │ │ │ └── geo/ │ │ │ │ │ │ └── v1/ │ │ │ │ │ │ └── country_alpha_3_code.proto │ │ │ │ │ └── money/ │ │ │ │ │ └── proto/ │ │ │ │ │ └── acme/ │ │ │ │ │ └── money/ │ │ │ │ │ └── v1/ │ │ │ │ │ ├── currency_code.proto │ │ │ │ │ └── money.proto │ │ │ │ └── finance/ │ │ │ │ ├── bond/ │ │ │ │ │ └── proto/ │ │ │ │ │ ├── root1/ │ │ │ │ │ │ └── acme/ │ │ │ │ │ │ └── bond/ │ │ │ │ │ │ ├── excluded/ │ │ │ │ │ │ │ └── v1/ │ │ │ │ │ │ │ └── excluded.proto │ │ │ │ │ │ └── real/ │ │ │ │ │ │ └── v1/ │ │ │ │ │ │ └── bond.proto │ │ │ │ │ └── root2/ │ │ │ │ │ └── acme/ │ │ │ │ │ └── bond/ │ │ │ │ │ └── v2/ │ │ │ │ │ └── bond.proto │ │ │ │ └── portfolio/ │ │ │ │ └── proto/ │ │ │ │ └── acme/ │ │ │ │ └── portfolio/ │ │ │ │ └── v1/ │ │ │ │ └── portfolio.proto │ │ │ └── usage.gen.go │ │ ├── bufprotopluginexec/ │ │ │ ├── binary_handler.go │ │ │ ├── bufprotopluginexec.go │ │ │ ├── generator.go │ │ │ ├── protoc_gen_swift_stderr_write_closer.go │ │ │ ├── protoc_proxy_handler.go │ │ │ ├── usage.gen.go │ │ │ ├── util.go │ │ │ ├── util_darwin.go │ │ │ ├── util_undarwin.go │ │ │ ├── version.go │ │ │ └── version_test.go │ │ ├── bufstudioagent/ │ │ │ ├── buffer_codec.go │ │ │ ├── bufstudioagent.go │ │ │ ├── bufstudioagent_test.go │ │ │ ├── plain_post_handler.go │ │ │ └── usage.gen.go │ │ ├── buftarget/ │ │ │ ├── bucket_targeting.go │ │ │ ├── buftarget.go │ │ │ ├── controlling_workspace.go │ │ │ ├── terminate.go │ │ │ └── usage.gen.go │ │ ├── buftesting/ │ │ │ ├── buftesting.go │ │ │ └── usage.gen.go │ │ ├── bufwkt/ │ │ │ ├── bufwktstore/ │ │ │ │ ├── bufwktstore.go │ │ │ │ ├── store.go │ │ │ │ └── usage.gen.go │ │ │ └── cmd/ │ │ │ └── wkt-go-data/ │ │ │ ├── main.go │ │ │ └── usage.gen.go │ │ └── bufworkspace/ │ │ ├── bufworkspace.go │ │ ├── malformed_dep.go │ │ ├── module_targeting.go │ │ ├── option.go │ │ ├── testdata/ │ │ │ └── basic/ │ │ │ ├── README.md │ │ │ ├── bsr/ │ │ │ │ └── buf.testing/ │ │ │ │ └── acme/ │ │ │ │ ├── date/ │ │ │ │ │ └── acme/ │ │ │ │ │ └── date/ │ │ │ │ │ └── v1/ │ │ │ │ │ └── date.proto │ │ │ │ └── extension/ │ │ │ │ └── acme/ │ │ │ │ └── extension/ │ │ │ │ └── v1/ │ │ │ │ └── extension.proto │ │ │ ├── scripts/ │ │ │ │ └── fakebuflock.bash │ │ │ ├── workspace_undeclared_dep/ │ │ │ │ ├── buf.yaml │ │ │ │ └── finance/ │ │ │ │ └── bond/ │ │ │ │ └── proto/ │ │ │ │ └── acme/ │ │ │ │ └── bond/ │ │ │ │ └── v2/ │ │ │ │ └── bond.proto │ │ │ ├── workspace_unused_dep/ │ │ │ │ ├── buf.yaml │ │ │ │ └── finance/ │ │ │ │ └── bond/ │ │ │ │ └── proto/ │ │ │ │ └── acme/ │ │ │ │ └── bond/ │ │ │ │ └── v2/ │ │ │ │ └── bond.proto │ │ │ ├── workspacev1/ │ │ │ │ ├── LICENSE │ │ │ │ ├── README.md │ │ │ │ ├── buf.work.yaml │ │ │ │ ├── common/ │ │ │ │ │ ├── geo/ │ │ │ │ │ │ └── proto/ │ │ │ │ │ │ ├── LICENSE │ │ │ │ │ │ ├── acme/ │ │ │ │ │ │ │ └── geo/ │ │ │ │ │ │ │ └── v1/ │ │ │ │ │ │ │ └── country_alpha_3_code.proto │ │ │ │ │ │ └── buf.yaml │ │ │ │ │ └── money/ │ │ │ │ │ └── proto/ │ │ │ │ │ ├── acme/ │ │ │ │ │ │ └── money/ │ │ │ │ │ │ └── v1/ │ │ │ │ │ │ ├── currency_code.proto │ │ │ │ │ │ └── money.proto │ │ │ │ │ ├── buf.md │ │ │ │ │ └── buf.yaml │ │ │ │ └── finance/ │ │ │ │ ├── bond/ │ │ │ │ │ └── proto/ │ │ │ │ │ ├── LICENSE │ │ │ │ │ ├── README.md │ │ │ │ │ ├── buf.yaml │ │ │ │ │ ├── root1/ │ │ │ │ │ │ └── acme/ │ │ │ │ │ │ └── bond/ │ │ │ │ │ │ ├── excluded/ │ │ │ │ │ │ │ └── v1/ │ │ │ │ │ │ │ └── excluded.proto │ │ │ │ │ │ └── real/ │ │ │ │ │ │ └── v1/ │ │ │ │ │ │ └── bond.proto │ │ │ │ │ └── root2/ │ │ │ │ │ └── acme/ │ │ │ │ │ └── bond/ │ │ │ │ │ └── v2/ │ │ │ │ │ └── bond.proto │ │ │ │ └── portfolio/ │ │ │ │ └── proto/ │ │ │ │ └── acme/ │ │ │ │ └── portfolio/ │ │ │ │ └── v1/ │ │ │ │ └── portfolio.proto │ │ │ ├── workspacev2/ │ │ │ │ ├── LICENSE │ │ │ │ ├── README.md │ │ │ │ ├── buf.yaml │ │ │ │ ├── common/ │ │ │ │ │ ├── geo/ │ │ │ │ │ │ └── proto/ │ │ │ │ │ │ ├── LICENSE │ │ │ │ │ │ └── acme/ │ │ │ │ │ │ └── geo/ │ │ │ │ │ │ └── v1/ │ │ │ │ │ │ └── country_alpha_3_code.proto │ │ │ │ │ └── money/ │ │ │ │ │ └── proto/ │ │ │ │ │ ├── acme/ │ │ │ │ │ │ └── money/ │ │ │ │ │ │ └── v1/ │ │ │ │ │ │ ├── currency_code.proto │ │ │ │ │ │ └── money.proto │ │ │ │ │ └── buf.md │ │ │ │ └── finance/ │ │ │ │ ├── bond/ │ │ │ │ │ └── proto/ │ │ │ │ │ ├── LICENSE │ │ │ │ │ ├── README.md │ │ │ │ │ └── acme/ │ │ │ │ │ └── bond/ │ │ │ │ │ ├── excluded/ │ │ │ │ │ │ └── v1/ │ │ │ │ │ │ └── excluded.proto │ │ │ │ │ ├── real/ │ │ │ │ │ │ └── v1/ │ │ │ │ │ │ └── bond.proto │ │ │ │ │ └── v2/ │ │ │ │ │ └── bond.proto │ │ │ │ └── portfolio/ │ │ │ │ └── proto/ │ │ │ │ └── acme/ │ │ │ │ └── portfolio/ │ │ │ │ └── v1/ │ │ │ │ └── portfolio.proto │ │ │ └── workspacev2_duplicate_path/ │ │ │ ├── buf.yaml │ │ │ ├── proto/ │ │ │ │ ├── shared/ │ │ │ │ │ └── prefix/ │ │ │ │ │ ├── bar/ │ │ │ │ │ │ └── v1/ │ │ │ │ │ │ └── bar.proto │ │ │ │ │ └── foo/ │ │ │ │ │ └── v1/ │ │ │ │ │ └── foo.proto │ │ │ │ └── shared1/ │ │ │ │ └── prefix/ │ │ │ │ ├── x/ │ │ │ │ │ └── x.proto │ │ │ │ └── y/ │ │ │ │ └── y.proto │ │ │ └── separate/ │ │ │ └── v1/ │ │ │ └── separate.proto │ │ ├── usage.gen.go │ │ ├── workspace.go │ │ ├── workspace_dep_manager.go │ │ ├── workspace_dep_manager_provider.go │ │ ├── workspace_provider.go │ │ ├── workspace_targeting.go │ │ └── workspace_test.go │ ├── bufpkg/ │ │ ├── bufanalysis/ │ │ │ ├── bufanalysis.go │ │ │ ├── bufanalysistesting/ │ │ │ │ ├── bufanalysistesting.go │ │ │ │ ├── bufanalysistesting_test.go │ │ │ │ └── usage.gen.go │ │ │ ├── file_annotation.go │ │ │ ├── file_annotation_set.go │ │ │ ├── print.go │ │ │ ├── usage.gen.go │ │ │ └── util.go │ │ ├── bufcheck/ │ │ │ ├── annotation.go │ │ │ ├── breaking_test.go │ │ │ ├── bufcheck.go │ │ │ ├── bufcheckserver/ │ │ │ │ ├── bufcheckserver.go │ │ │ │ ├── bufcheckserver_test.go │ │ │ │ ├── internal/ │ │ │ │ │ ├── bufcheckserverbuild/ │ │ │ │ │ │ ├── bufcheckserverbuild.go │ │ │ │ │ │ └── usage.gen.go │ │ │ │ │ ├── bufcheckserverhandle/ │ │ │ │ │ │ ├── breaking.go │ │ │ │ │ │ ├── breaking_util.go │ │ │ │ │ │ ├── bufcheckserverhandle.go │ │ │ │ │ │ ├── cardinality.go │ │ │ │ │ │ ├── field_default.go │ │ │ │ │ │ ├── field_default_test.go │ │ │ │ │ │ ├── lint.go │ │ │ │ │ │ ├── lint_util.go │ │ │ │ │ │ ├── tag_ranges.go │ │ │ │ │ │ ├── tag_ranges_test.go │ │ │ │ │ │ └── usage.gen.go │ │ │ │ │ ├── bufcheckserverutil/ │ │ │ │ │ │ ├── breaking.go │ │ │ │ │ │ ├── bufcheckserverutil.go │ │ │ │ │ │ ├── customfeatures/ │ │ │ │ │ │ │ └── customfeatures/ │ │ │ │ │ │ │ ├── customfeatures.go │ │ │ │ │ │ │ ├── customfeatures_test.go │ │ │ │ │ │ │ └── usage.gen.go │ │ │ │ │ │ ├── input_file.go │ │ │ │ │ │ ├── lint.go │ │ │ │ │ │ ├── request.go │ │ │ │ │ │ ├── response_writer.go │ │ │ │ │ │ ├── rule_spec_builder.go │ │ │ │ │ │ └── usage.gen.go │ │ │ │ │ └── buflintvalidate/ │ │ │ │ │ ├── adder.go │ │ │ │ │ ├── buflintvalidate.go │ │ │ │ │ ├── cel.go │ │ │ │ │ ├── cel_test.go │ │ │ │ │ ├── field.go │ │ │ │ │ ├── numeric.go │ │ │ │ │ ├── predefined_rules.go │ │ │ │ │ ├── usage.gen.go │ │ │ │ │ └── util.go │ │ │ │ ├── testdata/ │ │ │ │ │ └── lint/ │ │ │ │ │ └── service_pascal_case/ │ │ │ │ │ └── a.proto │ │ │ │ └── usage.gen.go │ │ │ ├── category.go │ │ │ ├── check_client_spec.go │ │ │ ├── client.go │ │ │ ├── config.go │ │ │ ├── file_info.go │ │ │ ├── internal/ │ │ │ │ ├── bufcheckopt/ │ │ │ │ │ ├── bufcheckopt.go │ │ │ │ │ └── usage.gen.go │ │ │ │ └── cmd/ │ │ │ │ ├── buf-plugin-duplicate-category/ │ │ │ │ │ ├── main.go │ │ │ │ │ └── usage.gen.go │ │ │ │ ├── buf-plugin-duplicate-rule/ │ │ │ │ │ ├── main.go │ │ │ │ │ └── usage.gen.go │ │ │ │ ├── buf-plugin-panic/ │ │ │ │ │ ├── main.go │ │ │ │ │ └── usage.gen.go │ │ │ │ ├── buf-plugin-protovalidate-ext/ │ │ │ │ │ ├── handlers.go │ │ │ │ │ ├── main.go │ │ │ │ │ ├── usage.gen.go │ │ │ │ │ └── util.go │ │ │ │ ├── buf-plugin-rpc-ext/ │ │ │ │ │ ├── main.go │ │ │ │ │ └── usage.gen.go │ │ │ │ └── buf-plugin-suffix/ │ │ │ │ ├── handlers.go │ │ │ │ ├── main.go │ │ │ │ └── usage.gen.go │ │ │ ├── lint_test.go │ │ │ ├── multi_client.go │ │ │ ├── multi_client_test.go │ │ │ ├── options_config.go │ │ │ ├── policy_config.go │ │ │ ├── print.go │ │ │ ├── rule.go │ │ │ ├── rules_config.go │ │ │ ├── runner_provider.go │ │ │ ├── testdata/ │ │ │ │ ├── breaking/ │ │ │ │ │ ├── current/ │ │ │ │ │ │ ├── breaking_custom_plugins/ │ │ │ │ │ │ │ ├── a.proto │ │ │ │ │ │ │ ├── b.proto │ │ │ │ │ │ │ └── buf.yaml │ │ │ │ │ │ ├── breaking_enum_no_delete/ │ │ │ │ │ │ │ ├── 1.proto │ │ │ │ │ │ │ ├── 2.proto │ │ │ │ │ │ │ └── buf.yaml │ │ │ │ │ │ ├── breaking_enum_same_json_format/ │ │ │ │ │ │ │ ├── 1.proto │ │ │ │ │ │ │ ├── 2.proto │ │ │ │ │ │ │ ├── 3.proto │ │ │ │ │ │ │ ├── 4.proto │ │ │ │ │ │ │ └── buf.yaml │ │ │ │ │ │ ├── breaking_enum_same_type/ │ │ │ │ │ │ │ ├── 1.proto │ │ │ │ │ │ │ ├── 2.proto │ │ │ │ │ │ │ ├── 3.proto │ │ │ │ │ │ │ ├── 4.proto │ │ │ │ │ │ │ └── buf.yaml │ │ │ │ │ │ ├── breaking_enum_value_no_delete/ │ │ │ │ │ │ │ ├── 1.proto │ │ │ │ │ │ │ ├── 2.proto │ │ │ │ │ │ │ └── buf.yaml │ │ │ │ │ │ ├── breaking_enum_value_no_delete_unless_name_reserved/ │ │ │ │ │ │ │ ├── 1.proto │ │ │ │ │ │ │ ├── 2.proto │ │ │ │ │ │ │ └── buf.yaml │ │ │ │ │ │ ├── breaking_enum_value_no_delete_unless_number_reserved/ │ │ │ │ │ │ │ ├── 1.proto │ │ │ │ │ │ │ ├── 2.proto │ │ │ │ │ │ │ └── buf.yaml │ │ │ │ │ │ ├── breaking_enum_value_same_name/ │ │ │ │ │ │ │ ├── 1.proto │ │ │ │ │ │ │ ├── 2.proto │ │ │ │ │ │ │ └── buf.yaml │ │ │ │ │ │ ├── breaking_extension_message_no_delete/ │ │ │ │ │ │ │ ├── 1.proto │ │ │ │ │ │ │ ├── 2.proto │ │ │ │ │ │ │ └── buf.yaml │ │ │ │ │ │ ├── breaking_extension_no_delete/ │ │ │ │ │ │ │ ├── 1.proto │ │ │ │ │ │ │ ├── 2.proto │ │ │ │ │ │ │ ├── 3.proto │ │ │ │ │ │ │ ├── 4.proto │ │ │ │ │ │ │ └── buf.yaml │ │ │ │ │ │ ├── breaking_field_no_delete/ │ │ │ │ │ │ │ ├── 1.proto │ │ │ │ │ │ │ ├── 2.proto │ │ │ │ │ │ │ ├── 3.proto │ │ │ │ │ │ │ └── buf.yaml │ │ │ │ │ │ ├── breaking_field_no_delete_unless_name_reserved/ │ │ │ │ │ │ │ ├── 1.proto │ │ │ │ │ │ │ ├── 2.proto │ │ │ │ │ │ │ ├── 3.proto │ │ │ │ │ │ │ └── buf.yaml │ │ │ │ │ │ ├── breaking_field_no_delete_unless_number_reserved/ │ │ │ │ │ │ │ ├── 1.proto │ │ │ │ │ │ │ ├── 2.proto │ │ │ │ │ │ │ ├── 3.proto │ │ │ │ │ │ │ └── buf.yaml │ │ │ │ │ │ ├── breaking_field_same_cardinality/ │ │ │ │ │ │ │ ├── 1.proto │ │ │ │ │ │ │ ├── 2.proto │ │ │ │ │ │ │ ├── 3.proto │ │ │ │ │ │ │ └── buf.yaml │ │ │ │ │ │ ├── breaking_field_same_cpp_string_type/ │ │ │ │ │ │ │ ├── 1.proto │ │ │ │ │ │ │ ├── 2.proto │ │ │ │ │ │ │ ├── 3.proto │ │ │ │ │ │ │ ├── buf.yaml │ │ │ │ │ │ │ └── google/ │ │ │ │ │ │ │ └── protobuf/ │ │ │ │ │ │ │ └── cpp_features.proto │ │ │ │ │ │ ├── breaking_field_same_ctype/ │ │ │ │ │ │ │ ├── 1.proto │ │ │ │ │ │ │ ├── 2.proto │ │ │ │ │ │ │ └── buf.yaml │ │ │ │ │ │ ├── breaking_field_same_default/ │ │ │ │ │ │ │ ├── 1.proto │ │ │ │ │ │ │ ├── 2.proto │ │ │ │ │ │ │ └── buf.yaml │ │ │ │ │ │ ├── breaking_field_same_java_utf8_validation/ │ │ │ │ │ │ │ ├── 1.proto │ │ │ │ │ │ │ ├── 2.proto │ │ │ │ │ │ │ ├── 3.proto │ │ │ │ │ │ │ ├── 4.proto │ │ │ │ │ │ │ ├── 5.proto │ │ │ │ │ │ │ ├── 6.proto │ │ │ │ │ │ │ └── buf.yaml │ │ │ │ │ │ ├── breaking_field_same_json_name/ │ │ │ │ │ │ │ ├── 1.proto │ │ │ │ │ │ │ ├── 2.proto │ │ │ │ │ │ │ └── buf.yaml │ │ │ │ │ │ ├── breaking_field_same_jstype/ │ │ │ │ │ │ │ ├── 1.proto │ │ │ │ │ │ │ ├── 2.proto │ │ │ │ │ │ │ └── buf.yaml │ │ │ │ │ │ ├── breaking_field_same_label/ │ │ │ │ │ │ │ ├── 1.proto │ │ │ │ │ │ │ ├── 2.proto │ │ │ │ │ │ │ ├── 3.proto │ │ │ │ │ │ │ └── buf.yaml │ │ │ │ │ │ ├── breaking_field_same_name/ │ │ │ │ │ │ │ ├── 1.proto │ │ │ │ │ │ │ ├── 2.proto │ │ │ │ │ │ │ └── buf.yaml │ │ │ │ │ │ ├── breaking_field_same_oneof/ │ │ │ │ │ │ │ ├── 1.proto │ │ │ │ │ │ │ ├── 2.proto │ │ │ │ │ │ │ └── buf.yaml │ │ │ │ │ │ ├── breaking_field_same_type/ │ │ │ │ │ │ │ ├── 1.proto │ │ │ │ │ │ │ ├── 2.proto │ │ │ │ │ │ │ ├── 3.proto │ │ │ │ │ │ │ └── buf.yaml │ │ │ │ │ │ ├── breaking_field_same_utf8_validation/ │ │ │ │ │ │ │ ├── 1.proto │ │ │ │ │ │ │ ├── 2.proto │ │ │ │ │ │ │ ├── 3.proto │ │ │ │ │ │ │ ├── 4.proto │ │ │ │ │ │ │ └── buf.yaml │ │ │ │ │ │ ├── breaking_field_wire_compatible_cardinality/ │ │ │ │ │ │ │ ├── 1.proto │ │ │ │ │ │ │ ├── 2.proto │ │ │ │ │ │ │ ├── 3.proto │ │ │ │ │ │ │ └── buf.yaml │ │ │ │ │ │ ├── breaking_field_wire_compatible_type/ │ │ │ │ │ │ │ ├── 2.proto │ │ │ │ │ │ │ ├── 3.proto │ │ │ │ │ │ │ └── buf.yaml │ │ │ │ │ │ ├── breaking_field_wire_json_compatible_cardinality/ │ │ │ │ │ │ │ ├── 1.proto │ │ │ │ │ │ │ ├── 2.proto │ │ │ │ │ │ │ ├── 3.proto │ │ │ │ │ │ │ └── buf.yaml │ │ │ │ │ │ ├── breaking_field_wire_json_compatible_type/ │ │ │ │ │ │ │ ├── 2.proto │ │ │ │ │ │ │ ├── 3.proto │ │ │ │ │ │ │ └── buf.yaml │ │ │ │ │ │ ├── breaking_file_no_delete/ │ │ │ │ │ │ │ ├── a/ │ │ │ │ │ │ │ │ └── b/ │ │ │ │ │ │ │ │ └── a_b.proto │ │ │ │ │ │ │ ├── buf.yaml │ │ │ │ │ │ │ └── sub/ │ │ │ │ │ │ │ └── a/ │ │ │ │ │ │ │ └── b/ │ │ │ │ │ │ │ └── sub_a_b.proto │ │ │ │ │ │ ├── breaking_file_no_delete_ignores/ │ │ │ │ │ │ │ ├── a/ │ │ │ │ │ │ │ │ └── b/ │ │ │ │ │ │ │ │ └── a_b.proto │ │ │ │ │ │ │ ├── buf.yaml │ │ │ │ │ │ │ └── sub/ │ │ │ │ │ │ │ └── a/ │ │ │ │ │ │ │ └── b/ │ │ │ │ │ │ │ └── sub_a_b.proto │ │ │ │ │ │ ├── breaking_file_no_delete_unstable/ │ │ │ │ │ │ │ ├── a/ │ │ │ │ │ │ │ │ └── v1beta1/ │ │ │ │ │ │ │ │ └── a.proto │ │ │ │ │ │ │ └── buf.yaml │ │ │ │ │ │ ├── breaking_file_same_package/ │ │ │ │ │ │ │ ├── a/ │ │ │ │ │ │ │ │ ├── a.proto │ │ │ │ │ │ │ │ └── b/ │ │ │ │ │ │ │ │ └── a_b.proto │ │ │ │ │ │ │ ├── buf.yaml │ │ │ │ │ │ │ ├── no_package.proto │ │ │ │ │ │ │ └── sub/ │ │ │ │ │ │ │ └── a/ │ │ │ │ │ │ │ └── b/ │ │ │ │ │ │ │ └── sub_a_b.proto │ │ │ │ │ │ ├── breaking_file_same_syntax/ │ │ │ │ │ │ │ ├── buf.yaml │ │ │ │ │ │ │ └── root/ │ │ │ │ │ │ │ ├── a/ │ │ │ │ │ │ │ │ ├── a.proto │ │ │ │ │ │ │ │ └── b/ │ │ │ │ │ │ │ │ └── a_b.proto │ │ │ │ │ │ │ ├── no_package.proto │ │ │ │ │ │ │ └── sub/ │ │ │ │ │ │ │ └── a/ │ │ │ │ │ │ │ └── b/ │ │ │ │ │ │ │ └── sub_a_b.proto │ │ │ │ │ │ ├── breaking_file_same_values/ │ │ │ │ │ │ │ ├── 1.proto │ │ │ │ │ │ │ ├── 2.proto │ │ │ │ │ │ │ └── buf.yaml │ │ │ │ │ │ ├── breaking_ignore_unstable_packages_delete_file/ │ │ │ │ │ │ │ ├── a/ │ │ │ │ │ │ │ │ └── v1beta1/ │ │ │ │ │ │ │ │ └── a.proto │ │ │ │ │ │ │ └── buf.yaml │ │ │ │ │ │ ├── breaking_ignore_unstable_packages_false/ │ │ │ │ │ │ │ ├── 1.proto │ │ │ │ │ │ │ ├── 2.proto │ │ │ │ │ │ │ ├── a/ │ │ │ │ │ │ │ │ ├── v1/ │ │ │ │ │ │ │ │ │ ├── 1.proto │ │ │ │ │ │ │ │ │ └── 2.proto │ │ │ │ │ │ │ │ └── v1beta1/ │ │ │ │ │ │ │ │ ├── 1.proto │ │ │ │ │ │ │ │ └── 2.proto │ │ │ │ │ │ │ ├── b/ │ │ │ │ │ │ │ │ ├── 1.proto │ │ │ │ │ │ │ │ └── 2.proto │ │ │ │ │ │ │ └── buf.yaml │ │ │ │ │ │ ├── breaking_ignore_unstable_packages_true/ │ │ │ │ │ │ │ ├── 1.proto │ │ │ │ │ │ │ ├── 2.proto │ │ │ │ │ │ │ ├── a/ │ │ │ │ │ │ │ │ ├── v1/ │ │ │ │ │ │ │ │ │ ├── 1.proto │ │ │ │ │ │ │ │ │ └── 2.proto │ │ │ │ │ │ │ │ └── v1beta1/ │ │ │ │ │ │ │ │ ├── 1.proto │ │ │ │ │ │ │ │ └── 2.proto │ │ │ │ │ │ │ ├── b/ │ │ │ │ │ │ │ │ ├── 1.proto │ │ │ │ │ │ │ │ └── 2.proto │ │ │ │ │ │ │ └── buf.yaml │ │ │ │ │ │ ├── breaking_int_enum/ │ │ │ │ │ │ │ ├── 1.proto │ │ │ │ │ │ │ └── 2.proto │ │ │ │ │ │ ├── breaking_message_enum/ │ │ │ │ │ │ │ ├── 1.proto │ │ │ │ │ │ │ └── 2.proto │ │ │ │ │ │ ├── breaking_message_int/ │ │ │ │ │ │ │ ├── 1.proto │ │ │ │ │ │ │ └── 2.proto │ │ │ │ │ │ ├── breaking_message_message/ │ │ │ │ │ │ │ ├── 1.proto │ │ │ │ │ │ │ └── 2.proto │ │ │ │ │ │ ├── breaking_message_no_delete/ │ │ │ │ │ │ │ ├── 1.proto │ │ │ │ │ │ │ ├── 2.proto │ │ │ │ │ │ │ └── buf.yaml │ │ │ │ │ │ ├── breaking_message_same_json_format/ │ │ │ │ │ │ │ ├── 1.proto │ │ │ │ │ │ │ ├── 2.proto │ │ │ │ │ │ │ ├── 3.proto │ │ │ │ │ │ │ ├── 4.proto │ │ │ │ │ │ │ └── buf.yaml │ │ │ │ │ │ ├── breaking_message_same_required_fields/ │ │ │ │ │ │ │ ├── 1.proto │ │ │ │ │ │ │ ├── 2.proto │ │ │ │ │ │ │ └── buf.yaml │ │ │ │ │ │ ├── breaking_message_same_values/ │ │ │ │ │ │ │ ├── 1.proto │ │ │ │ │ │ │ ├── 2.proto │ │ │ │ │ │ │ └── buf.yaml │ │ │ │ │ │ ├── breaking_oneof_no_delete/ │ │ │ │ │ │ │ ├── 1.proto │ │ │ │ │ │ │ ├── 2.proto │ │ │ │ │ │ │ └── buf.yaml │ │ │ │ │ │ ├── breaking_package_extension_no_delete/ │ │ │ │ │ │ │ ├── 1.proto │ │ │ │ │ │ │ ├── 2.proto │ │ │ │ │ │ │ ├── 3.proto │ │ │ │ │ │ │ ├── 4.proto │ │ │ │ │ │ │ └── buf.yaml │ │ │ │ │ │ ├── breaking_package_no_delete/ │ │ │ │ │ │ │ ├── a1.proto │ │ │ │ │ │ │ ├── a2.proto │ │ │ │ │ │ │ ├── b1.proto │ │ │ │ │ │ │ ├── b2.proto │ │ │ │ │ │ │ └── buf.yaml │ │ │ │ │ │ ├── breaking_package_service_no_delete/ │ │ │ │ │ │ │ ├── 1.proto │ │ │ │ │ │ │ ├── 2.proto │ │ │ │ │ │ │ └── buf.yaml │ │ │ │ │ │ ├── breaking_policy_disablebuiltin/ │ │ │ │ │ │ │ ├── a.proto │ │ │ │ │ │ │ ├── buf.yaml │ │ │ │ │ │ │ └── disablebuiltin.policy.yaml │ │ │ │ │ │ ├── breaking_policy_empty/ │ │ │ │ │ │ │ ├── a.proto │ │ │ │ │ │ │ ├── buf.yaml │ │ │ │ │ │ │ └── empty.policy.yaml │ │ │ │ │ │ ├── breaking_policy_local/ │ │ │ │ │ │ │ ├── a.proto │ │ │ │ │ │ │ ├── b.proto │ │ │ │ │ │ │ ├── buf.policy1.yaml │ │ │ │ │ │ │ ├── buf.policy2.yaml │ │ │ │ │ │ │ └── buf.yaml │ │ │ │ │ │ ├── breaking_reserved_enum_no_delete/ │ │ │ │ │ │ │ ├── 1.proto │ │ │ │ │ │ │ ├── 2.proto │ │ │ │ │ │ │ └── buf.yaml │ │ │ │ │ │ ├── breaking_reserved_message_no_delete/ │ │ │ │ │ │ │ ├── 1.proto │ │ │ │ │ │ │ ├── 2.proto │ │ │ │ │ │ │ └── buf.yaml │ │ │ │ │ │ ├── breaking_rpc_no_delete/ │ │ │ │ │ │ │ ├── 1.proto │ │ │ │ │ │ │ ├── 2.proto │ │ │ │ │ │ │ └── buf.yaml │ │ │ │ │ │ ├── breaking_rpc_same_values/ │ │ │ │ │ │ │ ├── 1.proto │ │ │ │ │ │ │ ├── 2.proto │ │ │ │ │ │ │ └── buf.yaml │ │ │ │ │ │ └── breaking_service_no_delete/ │ │ │ │ │ │ ├── 1.proto │ │ │ │ │ │ ├── 2.proto │ │ │ │ │ │ └── buf.yaml │ │ │ │ │ └── previous/ │ │ │ │ │ ├── breaking_custom_plugins/ │ │ │ │ │ │ ├── a.proto │ │ │ │ │ │ ├── b.proto │ │ │ │ │ │ └── buf.yaml │ │ │ │ │ ├── breaking_enum_no_delete/ │ │ │ │ │ │ ├── 1.proto │ │ │ │ │ │ └── 2.proto │ │ │ │ │ ├── breaking_enum_same_json_format/ │ │ │ │ │ │ ├── 1.proto │ │ │ │ │ │ ├── 2.proto │ │ │ │ │ │ ├── 3.proto │ │ │ │ │ │ └── 4.proto │ │ │ │ │ ├── breaking_enum_same_type/ │ │ │ │ │ │ ├── 1.proto │ │ │ │ │ │ ├── 2.proto │ │ │ │ │ │ ├── 3.proto │ │ │ │ │ │ └── 4.proto │ │ │ │ │ ├── breaking_enum_value_no_delete/ │ │ │ │ │ │ ├── 1.proto │ │ │ │ │ │ └── 2.proto │ │ │ │ │ ├── breaking_enum_value_no_delete_unless_name_reserved/ │ │ │ │ │ │ ├── 1.proto │ │ │ │ │ │ └── 2.proto │ │ │ │ │ ├── breaking_enum_value_no_delete_unless_number_reserved/ │ │ │ │ │ │ ├── 1.proto │ │ │ │ │ │ └── 2.proto │ │ │ │ │ ├── breaking_enum_value_same_name/ │ │ │ │ │ │ ├── 1.proto │ │ │ │ │ │ └── 2.proto │ │ │ │ │ ├── breaking_extension_message_no_delete/ │ │ │ │ │ │ ├── 1.proto │ │ │ │ │ │ └── 2.proto │ │ │ │ │ ├── breaking_extension_no_delete/ │ │ │ │ │ │ ├── 1.proto │ │ │ │ │ │ ├── 2.proto │ │ │ │ │ │ └── 3.proto │ │ │ │ │ ├── breaking_field_no_delete/ │ │ │ │ │ │ ├── 1.proto │ │ │ │ │ │ ├── 2.proto │ │ │ │ │ │ └── 3.proto │ │ │ │ │ ├── breaking_field_no_delete_unless_name_reserved/ │ │ │ │ │ │ ├── 1.proto │ │ │ │ │ │ ├── 2.proto │ │ │ │ │ │ └── 3.proto │ │ │ │ │ ├── breaking_field_no_delete_unless_number_reserved/ │ │ │ │ │ │ ├── 1.proto │ │ │ │ │ │ ├── 2.proto │ │ │ │ │ │ └── 3.proto │ │ │ │ │ ├── breaking_field_same_cardinality/ │ │ │ │ │ │ ├── 1.proto │ │ │ │ │ │ ├── 2.proto │ │ │ │ │ │ └── 3.proto │ │ │ │ │ ├── breaking_field_same_cpp_string_type/ │ │ │ │ │ │ ├── 1.proto │ │ │ │ │ │ ├── 2.proto │ │ │ │ │ │ ├── 3.proto │ │ │ │ │ │ └── google/ │ │ │ │ │ │ └── protobuf/ │ │ │ │ │ │ └── cpp_features.proto │ │ │ │ │ ├── breaking_field_same_ctype/ │ │ │ │ │ │ ├── 1.proto │ │ │ │ │ │ └── 2.proto │ │ │ │ │ ├── breaking_field_same_default/ │ │ │ │ │ │ ├── 1.proto │ │ │ │ │ │ └── 2.proto │ │ │ │ │ ├── breaking_field_same_java_utf8_validation/ │ │ │ │ │ │ ├── 1.proto │ │ │ │ │ │ ├── 2.proto │ │ │ │ │ │ ├── 3.proto │ │ │ │ │ │ ├── 4.proto │ │ │ │ │ │ ├── 5.proto │ │ │ │ │ │ └── 6.proto │ │ │ │ │ ├── breaking_field_same_json_name/ │ │ │ │ │ │ ├── 1.proto │ │ │ │ │ │ └── 2.proto │ │ │ │ │ ├── breaking_field_same_jstype/ │ │ │ │ │ │ ├── 1.proto │ │ │ │ │ │ └── 2.proto │ │ │ │ │ ├── breaking_field_same_label/ │ │ │ │ │ │ ├── 1.proto │ │ │ │ │ │ └── 2.proto │ │ │ │ │ ├── breaking_field_same_name/ │ │ │ │ │ │ ├── 1.proto │ │ │ │ │ │ └── 2.proto │ │ │ │ │ ├── breaking_field_same_oneof/ │ │ │ │ │ │ ├── 1.proto │ │ │ │ │ │ └── 2.proto │ │ │ │ │ ├── breaking_field_same_type/ │ │ │ │ │ │ ├── 1.proto │ │ │ │ │ │ ├── 2.proto │ │ │ │ │ │ └── 3.proto │ │ │ │ │ ├── breaking_field_same_utf8_validation/ │ │ │ │ │ │ ├── 1.proto │ │ │ │ │ │ ├── 2.proto │ │ │ │ │ │ ├── 3.proto │ │ │ │ │ │ └── 4.proto │ │ │ │ │ ├── breaking_field_wire_compatible_cardinality/ │ │ │ │ │ │ ├── 1.proto │ │ │ │ │ │ ├── 2.proto │ │ │ │ │ │ └── 3.proto │ │ │ │ │ ├── breaking_field_wire_compatible_type/ │ │ │ │ │ │ ├── 1.proto │ │ │ │ │ │ └── 2.proto │ │ │ │ │ ├── breaking_field_wire_json_compatible_cardinality/ │ │ │ │ │ │ ├── 1.proto │ │ │ │ │ │ ├── 2.proto │ │ │ │ │ │ └── 3.proto │ │ │ │ │ ├── breaking_field_wire_json_compatible_type/ │ │ │ │ │ │ ├── 1.proto │ │ │ │ │ │ └── 2.proto │ │ │ │ │ ├── breaking_file_no_delete/ │ │ │ │ │ │ ├── buf.yaml │ │ │ │ │ │ └── root/ │ │ │ │ │ │ ├── a/ │ │ │ │ │ │ │ ├── a.proto │ │ │ │ │ │ │ └── b/ │ │ │ │ │ │ │ └── a_b.proto │ │ │ │ │ │ ├── no_package.proto │ │ │ │ │ │ └── sub/ │ │ │ │ │ │ └── a/ │ │ │ │ │ │ └── b/ │ │ │ │ │ │ └── sub_a_b.proto │ │ │ │ │ ├── breaking_file_no_delete_ignores/ │ │ │ │ │ │ ├── buf.yaml │ │ │ │ │ │ └── root/ │ │ │ │ │ │ ├── a/ │ │ │ │ │ │ │ ├── a.proto │ │ │ │ │ │ │ ├── b/ │ │ │ │ │ │ │ │ └── a_b.proto │ │ │ │ │ │ │ └── c/ │ │ │ │ │ │ │ └── c.proto │ │ │ │ │ │ ├── no_package.proto │ │ │ │ │ │ └── sub/ │ │ │ │ │ │ └── a/ │ │ │ │ │ │ └── b/ │ │ │ │ │ │ └── sub_a_b.proto │ │ │ │ │ ├── breaking_file_no_delete_unstable/ │ │ │ │ │ │ └── a/ │ │ │ │ │ │ └── v1beta1/ │ │ │ │ │ │ ├── a.proto │ │ │ │ │ │ └── b.proto │ │ │ │ │ ├── breaking_file_same_package/ │ │ │ │ │ │ ├── buf.yaml │ │ │ │ │ │ └── root/ │ │ │ │ │ │ ├── a/ │ │ │ │ │ │ │ ├── a.proto │ │ │ │ │ │ │ └── b/ │ │ │ │ │ │ │ └── a_b.proto │ │ │ │ │ │ ├── no_package.proto │ │ │ │ │ │ └── sub/ │ │ │ │ │ │ └── a/ │ │ │ │ │ │ └── b/ │ │ │ │ │ │ └── sub_a_b.proto │ │ │ │ │ ├── breaking_file_same_syntax/ │ │ │ │ │ │ ├── buf.yaml │ │ │ │ │ │ └── root/ │ │ │ │ │ │ ├── a/ │ │ │ │ │ │ │ ├── a.proto │ │ │ │ │ │ │ └── b/ │ │ │ │ │ │ │ └── a_b.proto │ │ │ │ │ │ ├── no_package.proto │ │ │ │ │ │ └── sub/ │ │ │ │ │ │ └── a/ │ │ │ │ │ │ └── b/ │ │ │ │ │ │ └── sub_a_b.proto │ │ │ │ │ ├── breaking_file_same_values/ │ │ │ │ │ │ ├── 1.proto │ │ │ │ │ │ └── 2.proto │ │ │ │ │ ├── breaking_ignore_unstable_packages_delete_file/ │ │ │ │ │ │ └── a/ │ │ │ │ │ │ └── v1beta1/ │ │ │ │ │ │ ├── a.proto │ │ │ │ │ │ └── b.proto │ │ │ │ │ ├── breaking_ignore_unstable_packages_false/ │ │ │ │ │ │ ├── 1.proto │ │ │ │ │ │ ├── 2.proto │ │ │ │ │ │ ├── a/ │ │ │ │ │ │ │ ├── v1/ │ │ │ │ │ │ │ │ ├── 1.proto │ │ │ │ │ │ │ │ └── 2.proto │ │ │ │ │ │ │ └── v1beta1/ │ │ │ │ │ │ │ ├── 1.proto │ │ │ │ │ │ │ └── 2.proto │ │ │ │ │ │ └── b/ │ │ │ │ │ │ ├── 1.proto │ │ │ │ │ │ └── 2.proto │ │ │ │ │ ├── breaking_ignore_unstable_packages_true/ │ │ │ │ │ │ ├── 1.proto │ │ │ │ │ │ ├── 2.proto │ │ │ │ │ │ ├── a/ │ │ │ │ │ │ │ ├── v1/ │ │ │ │ │ │ │ │ ├── 1.proto │ │ │ │ │ │ │ │ └── 2.proto │ │ │ │ │ │ │ └── v1beta1/ │ │ │ │ │ │ │ ├── 1.proto │ │ │ │ │ │ │ └── 2.proto │ │ │ │ │ │ └── b/ │ │ │ │ │ │ ├── 1.proto │ │ │ │ │ │ └── 2.proto │ │ │ │ │ ├── breaking_int_enum/ │ │ │ │ │ │ ├── 1.proto │ │ │ │ │ │ └── 2.proto │ │ │ │ │ ├── breaking_message_enum/ │ │ │ │ │ │ ├── 1.proto │ │ │ │ │ │ └── 2.proto │ │ │ │ │ ├── breaking_message_int/ │ │ │ │ │ │ ├── 1.proto │ │ │ │ │ │ └── 2.proto │ │ │ │ │ ├── breaking_message_message/ │ │ │ │ │ │ ├── 1.proto │ │ │ │ │ │ └── 2.proto │ │ │ │ │ ├── breaking_message_no_delete/ │ │ │ │ │ │ ├── 1.proto │ │ │ │ │ │ └── 2.proto │ │ │ │ │ ├── breaking_message_same_json_format/ │ │ │ │ │ │ ├── 1.proto │ │ │ │ │ │ ├── 2.proto │ │ │ │ │ │ ├── 3.proto │ │ │ │ │ │ └── 4.proto │ │ │ │ │ ├── breaking_message_same_required_fields/ │ │ │ │ │ │ ├── 1.proto │ │ │ │ │ │ └── 2.proto │ │ │ │ │ ├── breaking_message_same_values/ │ │ │ │ │ │ ├── 1.proto │ │ │ │ │ │ └── 2.proto │ │ │ │ │ ├── breaking_oneof_no_delete/ │ │ │ │ │ │ ├── 1.proto │ │ │ │ │ │ └── 2.proto │ │ │ │ │ ├── breaking_package_extension_no_delete/ │ │ │ │ │ │ ├── 1.proto │ │ │ │ │ │ ├── 2.proto │ │ │ │ │ │ └── 3.proto │ │ │ │ │ ├── breaking_package_no_delete/ │ │ │ │ │ │ ├── a1.proto │ │ │ │ │ │ ├── b1.proto │ │ │ │ │ │ ├── buf.yaml │ │ │ │ │ │ └── c.proto │ │ │ │ │ ├── breaking_package_service_no_delete/ │ │ │ │ │ │ ├── 1.proto │ │ │ │ │ │ ├── 2.proto │ │ │ │ │ │ └── buf.yaml │ │ │ │ │ ├── breaking_policy_disablebuiltin/ │ │ │ │ │ │ ├── a.proto │ │ │ │ │ │ ├── buf.yaml │ │ │ │ │ │ └── disablebuiltin.policy.yaml │ │ │ │ │ ├── breaking_policy_empty/ │ │ │ │ │ │ ├── a.proto │ │ │ │ │ │ ├── buf.yaml │ │ │ │ │ │ └── empty.policy.yaml │ │ │ │ │ ├── breaking_policy_local/ │ │ │ │ │ │ ├── a.proto │ │ │ │ │ │ ├── b.proto │ │ │ │ │ │ └── buf.yaml │ │ │ │ │ ├── breaking_reserved_enum_no_delete/ │ │ │ │ │ │ ├── 1.proto │ │ │ │ │ │ └── 2.proto │ │ │ │ │ ├── breaking_reserved_message_no_delete/ │ │ │ │ │ │ ├── 1.proto │ │ │ │ │ │ └── 2.proto │ │ │ │ │ ├── breaking_rpc_no_delete/ │ │ │ │ │ │ ├── 1.proto │ │ │ │ │ │ └── 2.proto │ │ │ │ │ ├── breaking_rpc_same_values/ │ │ │ │ │ │ ├── 1.proto │ │ │ │ │ │ └── 2.proto │ │ │ │ │ └── breaking_service_no_delete/ │ │ │ │ │ ├── 1.proto │ │ │ │ │ └── 2.proto │ │ │ │ ├── lint/ │ │ │ │ │ ├── comment_ignores_cascade_off/ │ │ │ │ │ │ ├── a.proto │ │ │ │ │ │ ├── b.proto │ │ │ │ │ │ └── buf.yaml │ │ │ │ │ ├── comment_ignores_cascade_on/ │ │ │ │ │ │ ├── a.proto │ │ │ │ │ │ ├── b.proto │ │ │ │ │ │ └── buf.yaml │ │ │ │ │ ├── comment_ignores_multiple_fails/ │ │ │ │ │ │ ├── a.proto │ │ │ │ │ │ └── buf.yaml │ │ │ │ │ ├── comment_ignores_off/ │ │ │ │ │ │ ├── a.proto │ │ │ │ │ │ ├── b.proto │ │ │ │ │ │ └── buf.yaml │ │ │ │ │ ├── comment_ignores_on/ │ │ │ │ │ │ ├── a.proto │ │ │ │ │ │ ├── b.proto │ │ │ │ │ │ └── buf.yaml │ │ │ │ │ ├── comment_ignores_with_trailing_comment/ │ │ │ │ │ │ ├── a.proto │ │ │ │ │ │ └── buf.yaml │ │ │ │ │ ├── comments/ │ │ │ │ │ │ ├── a.proto │ │ │ │ │ │ ├── b.proto │ │ │ │ │ │ └── buf.yaml │ │ │ │ │ ├── custom_plugins/ │ │ │ │ │ │ ├── a.proto │ │ │ │ │ │ ├── b.proto │ │ │ │ │ │ └── buf.yaml │ │ │ │ │ ├── custom_wasm_plugins/ │ │ │ │ │ │ ├── a.proto │ │ │ │ │ │ ├── b.proto │ │ │ │ │ │ └── buf.yaml │ │ │ │ │ ├── directory_same_package/ │ │ │ │ │ │ ├── a.proto │ │ │ │ │ │ ├── buf.yaml │ │ │ │ │ │ ├── no_package.proto │ │ │ │ │ │ └── one/ │ │ │ │ │ │ ├── c.proto │ │ │ │ │ │ └── d.proto │ │ │ │ │ ├── editions_go_features/ │ │ │ │ │ │ ├── a.proto │ │ │ │ │ │ └── buf.yaml │ │ │ │ │ ├── enum_first_value_zero/ │ │ │ │ │ │ ├── a.proto │ │ │ │ │ │ └── buf.yaml │ │ │ │ │ ├── enum_no_allow_alias/ │ │ │ │ │ │ ├── a.proto │ │ │ │ │ │ └── buf.yaml │ │ │ │ │ ├── enum_pascal_case/ │ │ │ │ │ │ ├── a.proto │ │ │ │ │ │ └── buf.yaml │ │ │ │ │ ├── enum_value_prefix/ │ │ │ │ │ │ ├── a.proto │ │ │ │ │ │ └── buf.yaml │ │ │ │ │ ├── enum_value_upper_snake_case/ │ │ │ │ │ │ ├── a.proto │ │ │ │ │ │ └── buf.yaml │ │ │ │ │ ├── enum_zero_value_suffix/ │ │ │ │ │ │ ├── a.proto │ │ │ │ │ │ └── buf.yaml │ │ │ │ │ ├── enum_zero_value_suffix_custom/ │ │ │ │ │ │ ├── a.proto │ │ │ │ │ │ └── buf.yaml │ │ │ │ │ ├── field_lower_snake_case/ │ │ │ │ │ │ ├── a.proto │ │ │ │ │ │ ├── b.proto │ │ │ │ │ │ └── buf.yaml │ │ │ │ │ ├── field_no_descriptor/ │ │ │ │ │ │ ├── a.proto │ │ │ │ │ │ └── buf.yaml │ │ │ │ │ ├── field_not_required/ │ │ │ │ │ │ ├── a.proto │ │ │ │ │ │ ├── b.proto │ │ │ │ │ │ └── buf.yaml │ │ │ │ │ ├── file_lower_snake_case/ │ │ │ │ │ │ ├── 1.proto │ │ │ │ │ │ ├── B.proto │ │ │ │ │ │ ├── Foo.proto │ │ │ │ │ │ ├── a.proto │ │ │ │ │ │ ├── aBc.proto │ │ │ │ │ │ ├── aBc.txt │ │ │ │ │ │ ├── ab.proto │ │ │ │ │ │ ├── ab_c.proto │ │ │ │ │ │ ├── ab_c_.proto │ │ │ │ │ │ ├── buf.yaml │ │ │ │ │ │ └── fooBar.proto │ │ │ │ │ ├── ignores1/ │ │ │ │ │ │ ├── buf/ │ │ │ │ │ │ │ ├── bar/ │ │ │ │ │ │ │ │ ├── bar.proto │ │ │ │ │ │ │ │ └── bar2.proto │ │ │ │ │ │ │ ├── buf.proto │ │ │ │ │ │ │ └── foo/ │ │ │ │ │ │ │ ├── bar/ │ │ │ │ │ │ │ │ └── bar.proto │ │ │ │ │ │ │ ├── baz/ │ │ │ │ │ │ │ │ └── baz.proto │ │ │ │ │ │ │ └── buf.proto │ │ │ │ │ │ └── buf.yaml │ │ │ │ │ ├── ignores2/ │ │ │ │ │ │ ├── buf/ │ │ │ │ │ │ │ ├── bar/ │ │ │ │ │ │ │ │ ├── bar.proto │ │ │ │ │ │ │ │ └── bar2.proto │ │ │ │ │ │ │ ├── buf.proto │ │ │ │ │ │ │ └── foo/ │ │ │ │ │ │ │ ├── bar/ │ │ │ │ │ │ │ │ └── bar.proto │ │ │ │ │ │ │ ├── baz/ │ │ │ │ │ │ │ │ └── baz.proto │ │ │ │ │ │ │ └── buf.proto │ │ │ │ │ │ └── buf.yaml │ │ │ │ │ ├── ignores3/ │ │ │ │ │ │ ├── buf/ │ │ │ │ │ │ │ ├── bar/ │ │ │ │ │ │ │ │ ├── bar.proto │ │ │ │ │ │ │ │ └── bar2.proto │ │ │ │ │ │ │ ├── buf.proto │ │ │ │ │ │ │ └── foo/ │ │ │ │ │ │ │ ├── bar/ │ │ │ │ │ │ │ │ └── bar.proto │ │ │ │ │ │ │ ├── baz/ │ │ │ │ │ │ │ │ └── baz.proto │ │ │ │ │ │ │ └── buf.proto │ │ │ │ │ │ └── buf.yaml │ │ │ │ │ ├── ignores4/ │ │ │ │ │ │ ├── buf/ │ │ │ │ │ │ │ ├── bar/ │ │ │ │ │ │ │ │ ├── bar.proto │ │ │ │ │ │ │ │ └── bar2.proto │ │ │ │ │ │ │ ├── buf.proto │ │ │ │ │ │ │ └── foo/ │ │ │ │ │ │ │ ├── bar/ │ │ │ │ │ │ │ │ └── bar.proto │ │ │ │ │ │ │ ├── baz/ │ │ │ │ │ │ │ │ └── baz.proto │ │ │ │ │ │ │ └── buf.proto │ │ │ │ │ │ └── buf.yaml │ │ │ │ │ ├── import_no_public/ │ │ │ │ │ │ ├── a.proto │ │ │ │ │ │ ├── buf.yaml │ │ │ │ │ │ ├── one/ │ │ │ │ │ │ │ └── one.proto │ │ │ │ │ │ └── sub/ │ │ │ │ │ │ ├── sub1.proto │ │ │ │ │ │ └── sub2.proto │ │ │ │ │ ├── import_used/ │ │ │ │ │ │ ├── a.proto │ │ │ │ │ │ ├── buf.yaml │ │ │ │ │ │ ├── one/ │ │ │ │ │ │ │ └── one.proto │ │ │ │ │ │ └── sub/ │ │ │ │ │ │ ├── sub1.proto │ │ │ │ │ │ └── sub2.proto │ │ │ │ │ ├── lint_stable_package_import/ │ │ │ │ │ │ ├── api/ │ │ │ │ │ │ │ ├── baz/ │ │ │ │ │ │ │ │ └── baz.proto │ │ │ │ │ │ │ ├── v1/ │ │ │ │ │ │ │ │ └── foo.proto │ │ │ │ │ │ │ ├── v1alpha/ │ │ │ │ │ │ │ │ └── bar.proto │ │ │ │ │ │ │ └── v2/ │ │ │ │ │ │ │ └── foo.proto │ │ │ │ │ │ └── buf.yaml │ │ │ │ │ ├── message_pascal_case/ │ │ │ │ │ │ ├── a.proto │ │ │ │ │ │ └── buf.yaml │ │ │ │ │ ├── oneof_lower_snake_case/ │ │ │ │ │ │ ├── a.proto │ │ │ │ │ │ └── buf.yaml │ │ │ │ │ ├── package_defined/ │ │ │ │ │ │ ├── a/ │ │ │ │ │ │ │ └── no_package.proto │ │ │ │ │ │ ├── buf.yaml │ │ │ │ │ │ └── no_package.proto │ │ │ │ │ ├── package_directory_match/ │ │ │ │ │ │ ├── a/ │ │ │ │ │ │ │ └── b/ │ │ │ │ │ │ │ ├── a_b.proto │ │ │ │ │ │ │ └── a_c.proto │ │ │ │ │ │ ├── buf.yaml │ │ │ │ │ │ ├── no_package.proto │ │ │ │ │ │ └── sub/ │ │ │ │ │ │ └── a/ │ │ │ │ │ │ └── b/ │ │ │ │ │ │ └── a_b.proto │ │ │ │ │ ├── package_lower_snake_case/ │ │ │ │ │ │ ├── 1.proto │ │ │ │ │ │ ├── 2.proto │ │ │ │ │ │ ├── 3.proto │ │ │ │ │ │ ├── 4.proto │ │ │ │ │ │ ├── 5.proto │ │ │ │ │ │ ├── 6.proto │ │ │ │ │ │ ├── 7.proto │ │ │ │ │ │ ├── 8.proto │ │ │ │ │ │ ├── buf.yaml │ │ │ │ │ │ └── no_package.proto │ │ │ │ │ ├── package_no_import_cycle/ │ │ │ │ │ │ ├── a1.proto │ │ │ │ │ │ ├── a2.proto │ │ │ │ │ │ ├── b1.proto │ │ │ │ │ │ ├── b2.proto │ │ │ │ │ │ ├── buf.yaml │ │ │ │ │ │ ├── c1.proto │ │ │ │ │ │ ├── c2.proto │ │ │ │ │ │ ├── d1.proto │ │ │ │ │ │ ├── d2.proto │ │ │ │ │ │ ├── none1.proto │ │ │ │ │ │ └── none2.proto │ │ │ │ │ ├── package_same_directory/ │ │ │ │ │ │ ├── a.proto │ │ │ │ │ │ ├── buf.yaml │ │ │ │ │ │ └── one/ │ │ │ │ │ │ └── a.proto │ │ │ │ │ ├── package_same_directory_no_package/ │ │ │ │ │ │ ├── buf.yaml │ │ │ │ │ │ ├── no_package.proto │ │ │ │ │ │ └── one/ │ │ │ │ │ │ └── no_package.proto │ │ │ │ │ ├── package_same_option_value/ │ │ │ │ │ │ ├── a.proto │ │ │ │ │ │ ├── b.proto │ │ │ │ │ │ ├── buf.yaml │ │ │ │ │ │ └── sub/ │ │ │ │ │ │ ├── a.proto │ │ │ │ │ │ └── b.proto │ │ │ │ │ ├── package_version_suffix/ │ │ │ │ │ │ ├── buf.yaml │ │ │ │ │ │ ├── foo.proto │ │ │ │ │ │ ├── foo_bar.proto │ │ │ │ │ │ ├── foo_bar_v0beta1.proto │ │ │ │ │ │ ├── foo_bar_v1.proto │ │ │ │ │ │ ├── foo_bar_v1beta1.proto │ │ │ │ │ │ ├── foo_bar_v1test.proto │ │ │ │ │ │ ├── foo_bar_v1test_foo.proto │ │ │ │ │ │ ├── foo_bar_v1testfoo.proto │ │ │ │ │ │ ├── foo_bar_v2.proto │ │ │ │ │ │ ├── foo_bar_v2beta0.proto │ │ │ │ │ │ ├── foo_bar_vv1beta1.proto │ │ │ │ │ │ ├── no_package.proto │ │ │ │ │ │ └── v1.proto │ │ │ │ │ ├── policy_disablebuiltin/ │ │ │ │ │ │ ├── a.proto │ │ │ │ │ │ ├── buf.policydisablebuiltin.yaml │ │ │ │ │ │ └── buf.yaml │ │ │ │ │ ├── policy_empty/ │ │ │ │ │ │ ├── a.proto │ │ │ │ │ │ ├── buf.yaml │ │ │ │ │ │ └── empty.policy.yaml │ │ │ │ │ ├── policy_ignores1/ │ │ │ │ │ │ ├── buf/ │ │ │ │ │ │ │ ├── bar/ │ │ │ │ │ │ │ │ ├── bar.proto │ │ │ │ │ │ │ │ └── bar2.proto │ │ │ │ │ │ │ ├── buf.proto │ │ │ │ │ │ │ └── foo/ │ │ │ │ │ │ │ ├── bar/ │ │ │ │ │ │ │ │ └── bar.proto │ │ │ │ │ │ │ ├── baz/ │ │ │ │ │ │ │ │ └── baz.proto │ │ │ │ │ │ │ └── buf.proto │ │ │ │ │ │ ├── buf.yaml │ │ │ │ │ │ └── policy.yaml │ │ │ │ │ ├── policy_ignores2/ │ │ │ │ │ │ ├── buf/ │ │ │ │ │ │ │ ├── bar/ │ │ │ │ │ │ │ │ ├── bar.proto │ │ │ │ │ │ │ │ └── bar2.proto │ │ │ │ │ │ │ ├── buf.proto │ │ │ │ │ │ │ └── foo/ │ │ │ │ │ │ │ ├── bar/ │ │ │ │ │ │ │ │ └── bar.proto │ │ │ │ │ │ │ ├── baz/ │ │ │ │ │ │ │ │ └── baz.proto │ │ │ │ │ │ │ └── buf.proto │ │ │ │ │ │ ├── buf.yaml │ │ │ │ │ │ └── policy.yaml │ │ │ │ │ ├── policy_ignores3/ │ │ │ │ │ │ ├── buf/ │ │ │ │ │ │ │ ├── bar/ │ │ │ │ │ │ │ │ ├── bar.proto │ │ │ │ │ │ │ │ └── bar2.proto │ │ │ │ │ │ │ ├── buf.proto │ │ │ │ │ │ │ └── foo/ │ │ │ │ │ │ │ ├── bar/ │ │ │ │ │ │ │ │ └── bar.proto │ │ │ │ │ │ │ ├── baz/ │ │ │ │ │ │ │ │ └── baz.proto │ │ │ │ │ │ │ └── buf.proto │ │ │ │ │ │ ├── buf.yaml │ │ │ │ │ │ └── policy.yaml │ │ │ │ │ ├── policy_ignores4/ │ │ │ │ │ │ ├── buf/ │ │ │ │ │ │ │ ├── bar/ │ │ │ │ │ │ │ │ ├── bar.proto │ │ │ │ │ │ │ │ └── bar2.proto │ │ │ │ │ │ │ ├── buf.proto │ │ │ │ │ │ │ └── foo/ │ │ │ │ │ │ │ ├── bar/ │ │ │ │ │ │ │ │ └── bar.proto │ │ │ │ │ │ │ ├── baz/ │ │ │ │ │ │ │ │ └── baz.proto │ │ │ │ │ │ │ └── buf.proto │ │ │ │ │ │ ├── buf.yaml │ │ │ │ │ │ └── policy.yaml │ │ │ │ │ ├── policy_local/ │ │ │ │ │ │ ├── a.proto │ │ │ │ │ │ ├── b.proto │ │ │ │ │ │ ├── buf.policy1.yaml │ │ │ │ │ │ ├── buf.policy2.yaml │ │ │ │ │ │ └── buf.yaml │ │ │ │ │ ├── protovalidate/ │ │ │ │ │ │ ├── buf.work.yaml │ │ │ │ │ │ ├── proto/ │ │ │ │ │ │ │ ├── any.proto │ │ │ │ │ │ │ ├── bool.proto │ │ │ │ │ │ │ ├── buf.yaml │ │ │ │ │ │ │ ├── bytes.proto │ │ │ │ │ │ │ ├── cel_expression.proto │ │ │ │ │ │ │ ├── cel_field.proto │ │ │ │ │ │ │ ├── cel_message.proto │ │ │ │ │ │ │ ├── duration.proto │ │ │ │ │ │ │ ├── enum.proto │ │ │ │ │ │ │ ├── extension.proto │ │ │ │ │ │ │ ├── field.proto │ │ │ │ │ │ │ ├── field_mask.proto │ │ │ │ │ │ │ ├── map.proto │ │ │ │ │ │ │ ├── number.proto │ │ │ │ │ │ │ ├── oneof.proto │ │ │ │ │ │ │ ├── repeated.proto │ │ │ │ │ │ │ ├── string.proto │ │ │ │ │ │ │ └── timestamp.proto │ │ │ │ │ │ └── vendor/ │ │ │ │ │ │ └── protovalidate/ │ │ │ │ │ │ └── buf/ │ │ │ │ │ │ └── validate/ │ │ │ │ │ │ └── validate.proto │ │ │ │ │ ├── protovalidate_predefined/ │ │ │ │ │ │ ├── buf.yaml │ │ │ │ │ │ ├── import/ │ │ │ │ │ │ │ └── import.proto │ │ │ │ │ │ ├── proto/ │ │ │ │ │ │ │ ├── rules.proto │ │ │ │ │ │ │ └── test.proto │ │ │ │ │ │ └── vendor/ │ │ │ │ │ │ └── protovalidate/ │ │ │ │ │ │ └── buf/ │ │ │ │ │ │ └── validate/ │ │ │ │ │ │ └── validate.proto │ │ │ │ │ ├── rpc_no_streaming/ │ │ │ │ │ │ ├── a.proto │ │ │ │ │ │ └── buf.yaml │ │ │ │ │ ├── rpc_pascal_case/ │ │ │ │ │ │ ├── a.proto │ │ │ │ │ │ └── buf.yaml │ │ │ │ │ ├── rpc_request_response_unique/ │ │ │ │ │ │ ├── a.proto │ │ │ │ │ │ ├── b.proto │ │ │ │ │ │ └── buf.yaml │ │ │ │ │ ├── rpc_request_response_unique_allow_empty_requests/ │ │ │ │ │ │ ├── a.proto │ │ │ │ │ │ ├── b.proto │ │ │ │ │ │ └── buf.yaml │ │ │ │ │ ├── rpc_request_response_unique_allow_empty_requests_and_responses/ │ │ │ │ │ │ ├── a.proto │ │ │ │ │ │ ├── b.proto │ │ │ │ │ │ └── buf.yaml │ │ │ │ │ ├── rpc_request_response_unique_allow_empty_responses/ │ │ │ │ │ │ ├── a.proto │ │ │ │ │ │ ├── b.proto │ │ │ │ │ │ └── buf.yaml │ │ │ │ │ ├── rpc_request_response_unique_allow_same/ │ │ │ │ │ │ ├── a.proto │ │ │ │ │ │ ├── b.proto │ │ │ │ │ │ └── buf.yaml │ │ │ │ │ ├── rpc_request_response_unique_allow_same_and_empty_request_responses/ │ │ │ │ │ │ ├── a.proto │ │ │ │ │ │ ├── b.proto │ │ │ │ │ │ └── buf.yaml │ │ │ │ │ ├── rpc_standard_name/ │ │ │ │ │ │ ├── a.proto │ │ │ │ │ │ └── buf.yaml │ │ │ │ │ ├── rpc_standard_name_allow_empty/ │ │ │ │ │ │ ├── a.proto │ │ │ │ │ │ └── buf.yaml │ │ │ │ │ ├── service_pascal_case/ │ │ │ │ │ │ ├── a.proto │ │ │ │ │ │ └── buf.yaml │ │ │ │ │ ├── service_suffix/ │ │ │ │ │ │ ├── a.proto │ │ │ │ │ │ └── buf.yaml │ │ │ │ │ ├── service_suffix_custom/ │ │ │ │ │ │ ├── a.proto │ │ │ │ │ │ └── buf.yaml │ │ │ │ │ ├── syntax_specified/ │ │ │ │ │ │ ├── a/ │ │ │ │ │ │ │ ├── a.proto │ │ │ │ │ │ │ ├── a2.proto │ │ │ │ │ │ │ └── a3.proto │ │ │ │ │ │ └── buf.yaml │ │ │ │ │ └── v2/ │ │ │ │ │ └── ignores/ │ │ │ │ │ ├── buf.yaml │ │ │ │ │ ├── ignores1/ │ │ │ │ │ │ ├── bar1/ │ │ │ │ │ │ │ ├── bar.proto │ │ │ │ │ │ │ └── bar2.proto │ │ │ │ │ │ ├── buf1.proto │ │ │ │ │ │ └── foo1/ │ │ │ │ │ │ ├── bar/ │ │ │ │ │ │ │ └── bar.proto │ │ │ │ │ │ ├── baz/ │ │ │ │ │ │ │ └── baz.proto │ │ │ │ │ │ └── buf.proto │ │ │ │ │ ├── ignores2/ │ │ │ │ │ │ ├── bar2/ │ │ │ │ │ │ │ ├── bar.proto │ │ │ │ │ │ │ └── bar2.proto │ │ │ │ │ │ ├── buf2.proto │ │ │ │ │ │ └── foo2/ │ │ │ │ │ │ ├── bar/ │ │ │ │ │ │ │ └── bar.proto │ │ │ │ │ │ ├── baz/ │ │ │ │ │ │ │ └── baz.proto │ │ │ │ │ │ └── buf.proto │ │ │ │ │ └── ignores3/ │ │ │ │ │ ├── bar3/ │ │ │ │ │ │ ├── bar.proto │ │ │ │ │ │ └── bar2.proto │ │ │ │ │ ├── buf3.proto │ │ │ │ │ └── foo3/ │ │ │ │ │ ├── bar/ │ │ │ │ │ │ └── bar.proto │ │ │ │ │ ├── baz/ │ │ │ │ │ │ └── baz.proto │ │ │ │ │ └── buf.proto │ │ │ │ └── multi_client/ │ │ │ │ └── simple/ │ │ │ │ └── simple.proto │ │ │ ├── usage.gen.go │ │ │ └── util.go │ │ ├── bufcobra/ │ │ │ ├── bufcobra.go │ │ │ ├── config.go │ │ │ ├── markdown.go │ │ │ └── usage.gen.go │ │ ├── bufconfig/ │ │ │ ├── breaking_config.go │ │ │ ├── buf_gen_yaml_file.go │ │ │ ├── buf_gen_yaml_file_test.go │ │ │ ├── buf_lock_file.go │ │ │ ├── buf_lock_file_test.go │ │ │ ├── buf_work_yaml_file.go │ │ │ ├── buf_work_yaml_file_test.go │ │ │ ├── buf_yaml_file.go │ │ │ ├── buf_yaml_file_test.go │ │ │ ├── bufconfig.go │ │ │ ├── check_config.go │ │ │ ├── file.go │ │ │ ├── file_info.go │ │ │ ├── file_type.go │ │ │ ├── file_version.go │ │ │ ├── generate_config.go │ │ │ ├── generate_config_test.go │ │ │ ├── generate_managed_config.go │ │ │ ├── generate_managed_option.go │ │ │ ├── generate_plugin_config.go │ │ │ ├── generate_type_config.go │ │ │ ├── input_config.go │ │ │ ├── lint_config.go │ │ │ ├── module_config.go │ │ │ ├── object_data.go │ │ │ ├── paths.go │ │ │ ├── paths_test.go │ │ │ ├── plugin_config.go │ │ │ ├── policy_config.go │ │ │ ├── usage.gen.go │ │ │ ├── util.go │ │ │ ├── walk.go │ │ │ └── walk_test.go │ │ ├── bufconnect/ │ │ │ ├── bufconnect.go │ │ │ ├── errors.go │ │ │ ├── errors_test.go │ │ │ ├── interceptors.go │ │ │ ├── interceptors_test.go │ │ │ ├── netrc_token_provider.go │ │ │ ├── static_token_provider.go │ │ │ ├── static_token_provider_test.go │ │ │ └── usage.gen.go │ │ ├── bufimage/ │ │ │ ├── bufimage.go │ │ │ ├── bufimagefuzz/ │ │ │ │ ├── bufimagefuzz.go │ │ │ │ ├── bufimagefuzz_unix_test.go │ │ │ │ ├── corpus/ │ │ │ │ │ ├── alloptions.txtar │ │ │ │ │ ├── ccoptions.txtar │ │ │ │ │ ├── comment_ignores.txtar │ │ │ │ │ ├── comment_ignores_cascade.txtar │ │ │ │ │ ├── comments.txtar │ │ │ │ │ ├── csharpoptions.txtar │ │ │ │ │ ├── customoptions1.txtar │ │ │ │ │ ├── customoptionserror1.txtar │ │ │ │ │ ├── directory_same_package.txtar │ │ │ │ │ ├── empty.txtar │ │ │ │ │ ├── emptyoptions.txtar │ │ │ │ │ ├── enum_first_value_zero.txtar │ │ │ │ │ ├── enum_pascal_case.txtar │ │ │ │ │ ├── enum_value_prefix.txtar │ │ │ │ │ ├── enum_value_upper_snake_case.txtar │ │ │ │ │ ├── enum_zero_value_suffix.txtar │ │ │ │ │ ├── enum_zero_value_suffix_custom.txtar │ │ │ │ │ ├── ex1.txtar │ │ │ │ │ ├── ex2.txtar │ │ │ │ │ ├── ex3.txtar │ │ │ │ │ ├── field_lower_snake_case.txtar │ │ │ │ │ ├── field_no_descriptor.txtar │ │ │ │ │ ├── file_lower_snake_case.txtar │ │ │ │ │ ├── import_no_public.txtar │ │ │ │ │ ├── import_no_weak.txtar │ │ │ │ │ ├── import_used.txtar │ │ │ │ │ ├── javaoptions.txtar │ │ │ │ │ ├── jsoptions.txtar │ │ │ │ │ ├── message_pascal_case.txtar │ │ │ │ │ ├── objcoptions.txtar │ │ │ │ │ ├── oneof_lower_snake_case.txtar │ │ │ │ │ ├── optionpanic.txtar │ │ │ │ │ ├── package_defined.txtar │ │ │ │ │ ├── package_directory_match.txtar │ │ │ │ │ ├── package_lower_snake_case.txtar │ │ │ │ │ ├── package_same_directory.txtar │ │ │ │ │ ├── package_same_directory_no_package.txtar │ │ │ │ │ ├── package_same_option_value.txtar │ │ │ │ │ ├── package_version_suffix.txtar │ │ │ │ │ ├── packageversion.txtar │ │ │ │ │ ├── phpoptions.txtar │ │ │ │ │ ├── proto3optional1.txtar │ │ │ │ │ ├── rpc_no_streaming.txtar │ │ │ │ │ ├── rpc_pascal_case.txtar │ │ │ │ │ ├── rpc_standard_name.txtar │ │ │ │ │ ├── rpc_standard_name_allow_empty.txtar │ │ │ │ │ ├── rubyoptions.txtar │ │ │ │ │ ├── semicolons.txtar │ │ │ │ │ ├── service_pascal_case.txtar │ │ │ │ │ ├── service_suffix.txtar │ │ │ │ │ ├── service_suffix_custom.txtar │ │ │ │ │ ├── simple.txtar │ │ │ │ │ ├── syntax_specified.txtar │ │ │ │ │ ├── test.txtar │ │ │ │ │ └── wktimport.txtar │ │ │ │ └── usage.gen.go │ │ │ ├── bufimagemodify/ │ │ │ │ ├── bufimagemodify.go │ │ │ │ ├── bufimagemodify_test.go │ │ │ │ ├── field_option.go │ │ │ │ ├── file_option.go │ │ │ │ ├── internal/ │ │ │ │ │ ├── field_options_trie.go │ │ │ │ │ ├── internal.go │ │ │ │ │ ├── location_path_dfa.go │ │ │ │ │ ├── marksweeper.go │ │ │ │ │ └── usage.gen.go │ │ │ │ ├── override.go │ │ │ │ ├── testdata/ │ │ │ │ │ ├── alloptions/ │ │ │ │ │ │ └── a.proto │ │ │ │ │ ├── bar/ │ │ │ │ │ │ ├── bar_all/ │ │ │ │ │ │ │ ├── with_package.proto │ │ │ │ │ │ │ └── without_package.proto │ │ │ │ │ │ └── bar_empty/ │ │ │ │ │ │ ├── with_package.proto │ │ │ │ │ │ └── without_package.proto │ │ │ │ │ ├── ccoptions/ │ │ │ │ │ │ ├── a.proto │ │ │ │ │ │ └── b.proto │ │ │ │ │ ├── csharpoptions/ │ │ │ │ │ │ ├── double/ │ │ │ │ │ │ │ └── csharp.proto │ │ │ │ │ │ ├── single/ │ │ │ │ │ │ │ ├── csharp.proto │ │ │ │ │ │ │ └── override.proto │ │ │ │ │ │ ├── triple/ │ │ │ │ │ │ │ └── csharp.proto │ │ │ │ │ │ └── underscore/ │ │ │ │ │ │ └── csharp.proto │ │ │ │ │ ├── emptyoptions/ │ │ │ │ │ │ └── a.proto │ │ │ │ │ ├── foo/ │ │ │ │ │ │ ├── foo_all/ │ │ │ │ │ │ │ ├── with_package.proto │ │ │ │ │ │ │ └── without_package.proto │ │ │ │ │ │ └── foo_empty/ │ │ │ │ │ │ ├── with_package.proto │ │ │ │ │ │ └── without_package.proto │ │ │ │ │ ├── javaemptyoptions/ │ │ │ │ │ │ └── a.proto │ │ │ │ │ ├── javaoptions/ │ │ │ │ │ │ ├── java_file.proto │ │ │ │ │ │ └── override.proto │ │ │ │ │ ├── jsoptions/ │ │ │ │ │ │ └── a.proto │ │ │ │ │ ├── objcoptions/ │ │ │ │ │ │ ├── double/ │ │ │ │ │ │ │ └── objc.proto │ │ │ │ │ │ ├── gpb/ │ │ │ │ │ │ │ └── objc.proto │ │ │ │ │ │ ├── single/ │ │ │ │ │ │ │ ├── objc.proto │ │ │ │ │ │ │ └── override.proto │ │ │ │ │ │ ├── triple/ │ │ │ │ │ │ │ └── objc.proto │ │ │ │ │ │ └── unversioned/ │ │ │ │ │ │ └── objc.proto │ │ │ │ │ ├── packageversion/ │ │ │ │ │ │ ├── a.proto │ │ │ │ │ │ └── b.proto │ │ │ │ │ ├── phpoptions/ │ │ │ │ │ │ ├── double/ │ │ │ │ │ │ │ └── php.proto │ │ │ │ │ │ ├── reserved/ │ │ │ │ │ │ │ └── php.proto │ │ │ │ │ │ ├── single/ │ │ │ │ │ │ │ ├── override.proto │ │ │ │ │ │ │ └── php.proto │ │ │ │ │ │ ├── triple/ │ │ │ │ │ │ │ └── php.proto │ │ │ │ │ │ └── underscore/ │ │ │ │ │ │ └── php.proto │ │ │ │ │ ├── rubyoptions/ │ │ │ │ │ │ ├── double/ │ │ │ │ │ │ │ └── ruby.proto │ │ │ │ │ │ ├── single/ │ │ │ │ │ │ │ ├── override.proto │ │ │ │ │ │ │ └── ruby.proto │ │ │ │ │ │ ├── triple/ │ │ │ │ │ │ │ └── ruby.proto │ │ │ │ │ │ └── underscore/ │ │ │ │ │ │ └── ruby.proto │ │ │ │ │ └── wktimport/ │ │ │ │ │ └── a.proto │ │ │ │ └── usage.gen.go │ │ │ ├── bufimagetesting/ │ │ │ │ ├── bufimagetesting.go │ │ │ │ ├── bufimagetesting_test.go │ │ │ │ └── usage.gen.go │ │ │ ├── bufimageutil/ │ │ │ │ ├── bufimageutil.go │ │ │ │ ├── bufimageutil_test.go │ │ │ │ ├── image_filter.go │ │ │ │ ├── image_index.go │ │ │ │ ├── source_paths_remap.go │ │ │ │ ├── source_paths_remap_test.go │ │ │ │ ├── tags.go │ │ │ │ ├── testdata/ │ │ │ │ │ ├── any/ │ │ │ │ │ │ ├── a.proto │ │ │ │ │ │ ├── b.proto │ │ │ │ │ │ ├── c1.proto │ │ │ │ │ │ ├── c1.txtar │ │ │ │ │ │ ├── c2.proto │ │ │ │ │ │ ├── c2.txtar │ │ │ │ │ │ ├── c3.proto │ │ │ │ │ │ ├── c3.txtar │ │ │ │ │ │ ├── c4.proto │ │ │ │ │ │ ├── c4.txtar │ │ │ │ │ │ ├── d.proto │ │ │ │ │ │ ├── d.txtar │ │ │ │ │ │ ├── e.proto │ │ │ │ │ │ └── e.txtar │ │ │ │ │ ├── deps/ │ │ │ │ │ │ ├── a.proto │ │ │ │ │ │ ├── b.proto │ │ │ │ │ │ ├── c.proto │ │ │ │ │ │ ├── old.proto │ │ │ │ │ │ ├── test.EnumA.txtar │ │ │ │ │ │ ├── test.FieldA.txtar │ │ │ │ │ │ ├── test.IncludeWithExcludeExt.txtar │ │ │ │ │ │ ├── test.PublicOrder.txtar │ │ │ │ │ │ └── test.proto │ │ │ │ │ ├── empty/ │ │ │ │ │ │ ├── a.proto │ │ │ │ │ │ ├── b.proto │ │ │ │ │ │ ├── c.proto │ │ │ │ │ │ ├── d.proto │ │ │ │ │ │ ├── empty.exclude.txtar │ │ │ │ │ │ └── empty.include.txtar │ │ │ │ │ ├── exclude_import_deps/ │ │ │ │ │ │ ├── dep.proto │ │ │ │ │ │ ├── excluded.proto │ │ │ │ │ │ ├── opt.proto │ │ │ │ │ │ └── user.proto │ │ │ │ │ ├── extensions/ │ │ │ │ │ │ ├── a.proto │ │ │ │ │ │ ├── b.proto │ │ │ │ │ │ ├── extensions-excluded.txtar │ │ │ │ │ │ └── extensions.txtar │ │ │ │ │ ├── importmods/ │ │ │ │ │ │ ├── a.proto │ │ │ │ │ │ ├── noimports.txtar │ │ │ │ │ │ ├── public.proto │ │ │ │ │ │ ├── regular.proto │ │ │ │ │ │ ├── regular_public.txtar │ │ │ │ │ │ ├── regular_weak.txtar │ │ │ │ │ │ ├── weak.proto │ │ │ │ │ │ └── weak_public.txtar │ │ │ │ │ ├── imports/ │ │ │ │ │ │ ├── a.proto │ │ │ │ │ │ ├── bar.txtar │ │ │ │ │ │ ├── foo.txtar │ │ │ │ │ │ ├── foo_bar.txtar │ │ │ │ │ │ └── options.proto │ │ │ │ │ ├── nesting/ │ │ │ │ │ │ ├── a.proto │ │ │ │ │ │ ├── enum.exclude.txtar │ │ │ │ │ │ ├── enum.txtar │ │ │ │ │ │ ├── message.exclude.txtar │ │ │ │ │ │ ├── message.txtar │ │ │ │ │ │ ├── mixed.txtar │ │ │ │ │ │ ├── recursenested.exclude.txtar │ │ │ │ │ │ ├── recursenested.txtar │ │ │ │ │ │ ├── usingother.exclude.txtar │ │ │ │ │ │ └── usingother.txtar │ │ │ │ │ ├── oneofs/ │ │ │ │ │ │ ├── a.proto │ │ │ │ │ │ ├── pkg.Foo.exclude-bar.txtar │ │ │ │ │ │ └── pkg.Foo.exclude-partial.txtar │ │ │ │ │ ├── options/ │ │ │ │ │ │ ├── Files.txtar │ │ │ │ │ │ ├── a.proto │ │ │ │ │ │ ├── all-exclude-options.txtar │ │ │ │ │ │ ├── all-with-Files.txtar │ │ │ │ │ │ ├── all.exclude.txtar │ │ │ │ │ │ ├── all.txtar │ │ │ │ │ │ ├── options.foo.exclude.txtar │ │ │ │ │ │ ├── options.foo.include.txtar │ │ │ │ │ │ ├── options.only_file.txtar │ │ │ │ │ │ ├── options.proto │ │ │ │ │ │ ├── pkg.Foo.exclude.txtar │ │ │ │ │ │ ├── pkg.Foo.txtar │ │ │ │ │ │ ├── pkg.FooEnum.exclude.txtar │ │ │ │ │ │ ├── pkg.FooEnum.txtar │ │ │ │ │ │ ├── pkg.FooService.Do.exclude.txtar │ │ │ │ │ │ ├── pkg.FooService.Do.txtar │ │ │ │ │ │ ├── pkg.FooService.exclude-method-types.txtar │ │ │ │ │ │ ├── pkg.FooService.exclude.txtar │ │ │ │ │ │ ├── pkg.FooService.mixed.txtar │ │ │ │ │ │ ├── pkg.FooService.txtar │ │ │ │ │ │ └── pkg.Pkg.exclude.txtar │ │ │ │ │ ├── packages/ │ │ │ │ │ │ ├── bar.proto │ │ │ │ │ │ ├── baz1.proto │ │ │ │ │ │ ├── baz2.proto │ │ │ │ │ │ ├── foo.bar.baz.txtar │ │ │ │ │ │ ├── foo.bar.txtar │ │ │ │ │ │ ├── foo.proto │ │ │ │ │ │ ├── foo.txtar │ │ │ │ │ │ ├── nopackage.proto │ │ │ │ │ │ ├── options.proto │ │ │ │ │ │ ├── other.proto │ │ │ │ │ │ └── root.txtar │ │ │ │ │ ├── sourcecodeinfo/ │ │ │ │ │ │ ├── Bar.txtar │ │ │ │ │ │ ├── Baz.txtar │ │ │ │ │ │ ├── Do.txtar │ │ │ │ │ │ ├── Foo+Ext.txtar │ │ │ │ │ │ ├── Foo.txtar │ │ │ │ │ │ ├── NestedFoo.txtar │ │ │ │ │ │ ├── Quz.txtar │ │ │ │ │ │ ├── Svc.txtar │ │ │ │ │ │ ├── all.txtar │ │ │ │ │ │ └── test.proto │ │ │ │ │ └── unuseddeps/ │ │ │ │ │ ├── a.proto │ │ │ │ │ ├── a.txtar │ │ │ │ │ ├── ab.txtar │ │ │ │ │ ├── b.proto │ │ │ │ │ └── c.proto │ │ │ │ └── usage.gen.go │ │ │ ├── build_image.go │ │ │ ├── build_image_test.go │ │ │ ├── build_image_unix_test.go │ │ │ ├── image.go │ │ │ ├── image_file.go │ │ │ ├── image_test.go │ │ │ ├── import_tracker.go │ │ │ ├── module_image_file_info.go │ │ │ ├── parser_accessor_handler.go │ │ │ ├── testdata/ │ │ │ │ ├── customoptions1/ │ │ │ │ │ └── a.proto │ │ │ │ ├── customoptionserror1/ │ │ │ │ │ ├── a.proto │ │ │ │ │ └── b.proto │ │ │ │ ├── cyclicimport/ │ │ │ │ │ ├── a/ │ │ │ │ │ │ └── a.proto │ │ │ │ │ └── b/ │ │ │ │ │ └── b.proto │ │ │ │ ├── duplicatesyntheticoneofs/ │ │ │ │ │ ├── a1.proto │ │ │ │ │ └── a2.proto │ │ │ │ ├── notamessagetype/ │ │ │ │ │ └── a.proto │ │ │ │ ├── optionpanic/ │ │ │ │ │ ├── options/ │ │ │ │ │ │ └── option.proto │ │ │ │ │ └── proto/ │ │ │ │ │ └── test.proto │ │ │ │ ├── proto3optional1/ │ │ │ │ │ └── a.proto │ │ │ │ ├── semicolons/ │ │ │ │ │ └── a.proto │ │ │ │ ├── spacebetweennumberandid/ │ │ │ │ │ └── a.proto │ │ │ │ └── trailingcomments/ │ │ │ │ └── a.proto │ │ │ ├── usage.gen.go │ │ │ ├── util.go │ │ │ ├── util_test.go │ │ │ ├── validate.go │ │ │ └── well_known_type_image_file_info.go │ │ ├── bufmodule/ │ │ │ ├── added_module.go │ │ │ ├── bufmodule.go │ │ │ ├── bufmodule_test.go │ │ │ ├── bufmoduleapi/ │ │ │ │ ├── bufmoduleapi.go │ │ │ │ ├── cmd/ │ │ │ │ │ └── buf-legacyfederation-go-data/ │ │ │ │ │ ├── main.go │ │ │ │ │ └── usage.gen.go │ │ │ │ ├── commit_provider.go │ │ │ │ ├── convert.go │ │ │ │ ├── digest_for_commit_id.go │ │ │ │ ├── errors.go │ │ │ │ ├── graph_provider.go │ │ │ │ ├── module_data_provider.go │ │ │ │ ├── module_key_for_universal_proto_commit.go │ │ │ │ ├── module_key_provider.go │ │ │ │ ├── registry.go │ │ │ │ ├── universal_proto_commit.go │ │ │ │ ├── universal_proto_content.go │ │ │ │ ├── universal_proto_file.go │ │ │ │ ├── uploader.go │ │ │ │ ├── usage.gen.go │ │ │ │ ├── v1_proto_module_provider.go │ │ │ │ └── v1_proto_owner_provider.go │ │ │ ├── bufmodulecache/ │ │ │ │ ├── base_provider.go │ │ │ │ ├── bufmodulecache.go │ │ │ │ ├── bufmodulecache_test.go │ │ │ │ ├── commit_provider.go │ │ │ │ ├── module_data_provider.go │ │ │ │ └── usage.gen.go │ │ │ ├── bufmoduledebug/ │ │ │ │ ├── bufmoduledebug.go │ │ │ │ └── usage.gen.go │ │ │ ├── bufmodulestore/ │ │ │ │ ├── bufmodulestore.go │ │ │ │ ├── commit_store.go │ │ │ │ ├── commit_store_test.go │ │ │ │ ├── module_data_store.go │ │ │ │ ├── module_data_store_test.go │ │ │ │ ├── usage.gen.go │ │ │ │ └── util.go │ │ │ ├── bufmoduletesting/ │ │ │ │ ├── bufmoduletesting.go │ │ │ │ ├── cmd/ │ │ │ │ │ ├── buf-digest/ │ │ │ │ │ │ ├── digest.go │ │ │ │ │ │ └── usage.gen.go │ │ │ │ │ └── buf-new-commit-id/ │ │ │ │ │ ├── newcommitid.go │ │ │ │ │ └── usage.gen.go │ │ │ │ └── usage.gen.go │ │ │ ├── commit.go │ │ │ ├── commit_key.go │ │ │ ├── commit_provider.go │ │ │ ├── digest.go │ │ │ ├── errors.go │ │ │ ├── file.go │ │ │ ├── file_info.go │ │ │ ├── file_type.go │ │ │ ├── graph_provider.go │ │ │ ├── module.go │ │ │ ├── module_data.go │ │ │ ├── module_data_provider.go │ │ │ ├── module_dep.go │ │ │ ├── module_key.go │ │ │ ├── module_key_provider.go │ │ │ ├── module_read_bucket.go │ │ │ ├── module_set.go │ │ │ ├── module_set_builder.go │ │ │ ├── module_visibility.go │ │ │ ├── object_data.go │ │ │ ├── paths.go │ │ │ ├── proto_file_tracker.go │ │ │ ├── registry_commit_id.go │ │ │ ├── remote_dep.go │ │ │ ├── uploader.go │ │ │ └── usage.gen.go │ │ ├── bufparse/ │ │ │ ├── bufparse.go │ │ │ ├── errors.go │ │ │ ├── full_name.go │ │ │ ├── parse.go │ │ │ ├── ref.go │ │ │ └── usage.gen.go │ │ ├── bufplugin/ │ │ │ ├── bufplugin.go │ │ │ ├── bufpluginapi/ │ │ │ │ ├── bufpluginapi.go │ │ │ │ ├── convert.go │ │ │ │ ├── plugin_data_provider.go │ │ │ │ ├── plugin_key_provider.go │ │ │ │ ├── uploader.go │ │ │ │ └── usage.gen.go │ │ │ ├── bufplugincache/ │ │ │ │ ├── bufplugincache.go │ │ │ │ ├── plugin_data_provider.go │ │ │ │ └── usage.gen.go │ │ │ ├── bufpluginstore/ │ │ │ │ ├── bufpluginstore.go │ │ │ │ ├── policy_data_store.go │ │ │ │ └── usage.gen.go │ │ │ ├── commit.go │ │ │ ├── digest.go │ │ │ ├── errors.go │ │ │ ├── plugin.go │ │ │ ├── plugin_data.go │ │ │ ├── plugin_data_provider.go │ │ │ ├── plugin_key.go │ │ │ ├── plugin_key_provider.go │ │ │ ├── plugin_type.go │ │ │ ├── plugin_visibility.go │ │ │ ├── uploader.go │ │ │ └── usage.gen.go │ │ ├── bufpolicy/ │ │ │ ├── bufpolicy.go │ │ │ ├── bufpolicyapi/ │ │ │ │ ├── bufpolicyapi.go │ │ │ │ ├── convert.go │ │ │ │ ├── policy_data_provider.go │ │ │ │ ├── policy_key_provider.go │ │ │ │ ├── uploader.go │ │ │ │ └── usage.gen.go │ │ │ ├── bufpolicycache/ │ │ │ │ ├── bufpolicycache.go │ │ │ │ ├── policy_data_provider.go │ │ │ │ └── usage.gen.go │ │ │ ├── bufpolicyconfig/ │ │ │ │ ├── buf_policy_yaml_file.go │ │ │ │ ├── buf_policy_yaml_file_test.go │ │ │ │ ├── bufpolicyconfig.go │ │ │ │ ├── convert.go │ │ │ │ ├── file.go │ │ │ │ └── usage.gen.go │ │ │ ├── bufpolicystore/ │ │ │ │ ├── buf_policy_store.go │ │ │ │ ├── bufpolicystore.go │ │ │ │ └── usage.gen.go │ │ │ ├── commit.go │ │ │ ├── digest.go │ │ │ ├── digest_test.go │ │ │ ├── errors.go │ │ │ ├── policy.go │ │ │ ├── policy_config.go │ │ │ ├── policy_config_test.go │ │ │ ├── policy_data.go │ │ │ ├── policy_data_provider.go │ │ │ ├── policy_key.go │ │ │ ├── policy_key_provider.go │ │ │ ├── policy_plugin_data_provider.go │ │ │ ├── policy_plugin_key_provider.go │ │ │ ├── policy_visibility.go │ │ │ ├── uploader.go │ │ │ └── usage.gen.go │ │ ├── bufprotocompile/ │ │ │ ├── bufprotocompile.go │ │ │ └── usage.gen.go │ │ ├── bufprotoplugin/ │ │ │ ├── bufprotoplugin.go │ │ │ ├── bufprotoplugin_test.go │ │ │ ├── bufprotopluginos/ │ │ │ │ ├── bufprotopluginos.go │ │ │ │ ├── cleaner.go │ │ │ │ ├── response_writer.go │ │ │ │ └── usage.gen.go │ │ │ ├── generator.go │ │ │ ├── response_writer.go │ │ │ └── usage.gen.go │ │ ├── bufprotosource/ │ │ │ ├── bufprotosource.go │ │ │ ├── bufprotosource_test.go │ │ │ ├── descriptor.go │ │ │ ├── enum.go │ │ │ ├── enum_range.go │ │ │ ├── enum_value.go │ │ │ ├── field.go │ │ │ ├── file.go │ │ │ ├── file_import.go │ │ │ ├── files.go │ │ │ ├── location.go │ │ │ ├── location_descriptor.go │ │ │ ├── location_store.go │ │ │ ├── merge_comment_location.go │ │ │ ├── message.go │ │ │ ├── message_range.go │ │ │ ├── method.go │ │ │ ├── named_descriptor.go │ │ │ ├── oneof.go │ │ │ ├── option_extension_descriptor.go │ │ │ ├── option_extension_descriptor_test.go │ │ │ ├── paths.go │ │ │ ├── reserved_name.go │ │ │ ├── service.go │ │ │ ├── tag_range_test.go │ │ │ ├── testdata/ │ │ │ │ └── nested/ │ │ │ │ └── foo.proto │ │ │ └── usage.gen.go │ │ ├── bufreflect/ │ │ │ ├── bufreflect.go │ │ │ └── usage.gen.go │ │ ├── bufregistryapi/ │ │ │ ├── bufregistryapimodule/ │ │ │ │ ├── bufregistryapimodule.go │ │ │ │ └── usage.gen.go │ │ │ ├── bufregistryapiowner/ │ │ │ │ ├── bufregistryapiowner.go │ │ │ │ └── usage.gen.go │ │ │ ├── bufregistryapiplugin/ │ │ │ │ ├── bufregistryapiplugin.go │ │ │ │ └── usage.gen.go │ │ │ └── bufregistryapipolicy/ │ │ │ ├── bufregistryapipolicy.go │ │ │ └── usage.gen.go │ │ ├── bufremoteplugin/ │ │ │ ├── bufremoteplugin.go │ │ │ ├── bufremoteplugin_test.go │ │ │ ├── bufremotepluginconfig/ │ │ │ │ ├── bufremotepluginconfig.go │ │ │ │ ├── bufremotepluginconfig_test.go │ │ │ │ ├── config.go │ │ │ │ ├── get.go │ │ │ │ ├── testdata/ │ │ │ │ │ ├── failure/ │ │ │ │ │ │ ├── invalid-empty-plugin-version.yaml │ │ │ │ │ │ ├── invalid-empty-version.yaml │ │ │ │ │ │ ├── invalid-multiple-registries.yaml │ │ │ │ │ │ └── invalid-plugin-version.yaml │ │ │ │ │ └── success/ │ │ │ │ │ ├── cargo/ │ │ │ │ │ │ └── buf.plugin.yaml │ │ │ │ │ ├── cmake/ │ │ │ │ │ │ └── buf.plugin.yaml │ │ │ │ │ ├── go/ │ │ │ │ │ │ └── buf.plugin.yaml │ │ │ │ │ ├── go-empty-registry/ │ │ │ │ │ │ └── buf.plugin.yaml │ │ │ │ │ ├── maven/ │ │ │ │ │ │ └── buf.plugin.yaml │ │ │ │ │ ├── npm/ │ │ │ │ │ │ └── buf.plugin.yaml │ │ │ │ │ ├── nuget/ │ │ │ │ │ │ └── buf.plugin.yaml │ │ │ │ │ ├── options/ │ │ │ │ │ │ └── buf.plugin.yaml │ │ │ │ │ ├── python/ │ │ │ │ │ │ └── buf.plugin.yaml │ │ │ │ │ └── swift/ │ │ │ │ │ └── buf.plugin.yaml │ │ │ │ └── usage.gen.go │ │ │ ├── bufremoteplugindocker/ │ │ │ │ ├── bufremoteplugindocker.go │ │ │ │ ├── docker.go │ │ │ │ ├── docker_test.go │ │ │ │ ├── registry_auth_config.go │ │ │ │ ├── registry_auth_config_test.go │ │ │ │ ├── testdata/ │ │ │ │ │ └── success/ │ │ │ │ │ └── Dockerfile │ │ │ │ └── usage.gen.go │ │ │ ├── bufremotepluginref/ │ │ │ │ ├── bufremotepluginref.go │ │ │ │ ├── bufremotepluginref_test.go │ │ │ │ ├── plugin_identity.go │ │ │ │ ├── plugin_reference.go │ │ │ │ └── usage.gen.go │ │ │ ├── dotnet_target_framework.go │ │ │ ├── dotnet_target_platform_test.go │ │ │ ├── plugin.go │ │ │ └── usage.gen.go │ │ └── buftransport/ │ │ ├── buftransport.go │ │ └── usage.gen.go │ ├── gen/ │ │ ├── data/ │ │ │ ├── datalegacyfederation/ │ │ │ │ ├── datalegacyfederation.gen.go │ │ │ │ └── usage.gen.go │ │ │ └── datawkt/ │ │ │ ├── datawkt.gen.go │ │ │ └── usage.gen.go │ │ └── proto/ │ │ ├── connect/ │ │ │ └── buf/ │ │ │ └── alpha/ │ │ │ ├── audit/ │ │ │ │ └── v1alpha1/ │ │ │ │ └── auditv1alpha1connect/ │ │ │ │ ├── service.connect.go │ │ │ │ └── usage.gen.go │ │ │ ├── registry/ │ │ │ │ └── v1alpha1/ │ │ │ │ └── registryv1alpha1connect/ │ │ │ │ ├── admin.connect.go │ │ │ │ ├── authn.connect.go │ │ │ │ ├── authz.connect.go │ │ │ │ ├── convert.connect.go │ │ │ │ ├── display.connect.go │ │ │ │ ├── doc.connect.go │ │ │ │ ├── download.connect.go │ │ │ │ ├── github.connect.go │ │ │ │ ├── image.connect.go │ │ │ │ ├── jsonschema.connect.go │ │ │ │ ├── organization.connect.go │ │ │ │ ├── owner.connect.go │ │ │ │ ├── plugin_curation.connect.go │ │ │ │ ├── push.connect.go │ │ │ │ ├── reference.connect.go │ │ │ │ ├── repository.connect.go │ │ │ │ ├── repository_branch.connect.go │ │ │ │ ├── repository_commit.connect.go │ │ │ │ ├── repository_tag.connect.go │ │ │ │ ├── resolve.connect.go │ │ │ │ ├── resource.connect.go │ │ │ │ ├── schema.connect.go │ │ │ │ ├── scim_token.connect.go │ │ │ │ ├── search.connect.go │ │ │ │ ├── studio.connect.go │ │ │ │ ├── studio_request.connect.go │ │ │ │ ├── token.connect.go │ │ │ │ ├── usage.gen.go │ │ │ │ ├── user.connect.go │ │ │ │ └── webhook.connect.go │ │ │ └── webhook/ │ │ │ └── v1alpha1/ │ │ │ └── webhookv1alpha1connect/ │ │ │ ├── event.connect.go │ │ │ └── usage.gen.go │ │ └── go/ │ │ ├── buf/ │ │ │ └── alpha/ │ │ │ ├── audit/ │ │ │ │ └── v1alpha1/ │ │ │ │ ├── event.pb.go │ │ │ │ ├── service.pb.go │ │ │ │ └── usage.gen.go │ │ │ ├── breaking/ │ │ │ │ └── v1/ │ │ │ │ ├── config.pb.go │ │ │ │ └── usage.gen.go │ │ │ ├── image/ │ │ │ │ └── v1/ │ │ │ │ ├── image.pb.go │ │ │ │ └── usage.gen.go │ │ │ ├── lint/ │ │ │ │ └── v1/ │ │ │ │ ├── config.pb.go │ │ │ │ └── usage.gen.go │ │ │ ├── module/ │ │ │ │ └── v1alpha1/ │ │ │ │ ├── module.pb.go │ │ │ │ └── usage.gen.go │ │ │ ├── registry/ │ │ │ │ └── v1alpha1/ │ │ │ │ ├── admin.pb.go │ │ │ │ ├── authn.pb.go │ │ │ │ ├── authz.pb.go │ │ │ │ ├── convert.pb.go │ │ │ │ ├── display.pb.go │ │ │ │ ├── doc.pb.go │ │ │ │ ├── download.pb.go │ │ │ │ ├── git_metadata.pb.go │ │ │ │ ├── github.pb.go │ │ │ │ ├── image.pb.go │ │ │ │ ├── jsonschema.pb.go │ │ │ │ ├── module.pb.go │ │ │ │ ├── organization.pb.go │ │ │ │ ├── owner.pb.go │ │ │ │ ├── plugin_curation.pb.go │ │ │ │ ├── push.pb.go │ │ │ │ ├── reference.pb.go │ │ │ │ ├── repository.pb.go │ │ │ │ ├── repository_branch.pb.go │ │ │ │ ├── repository_commit.pb.go │ │ │ │ ├── repository_tag.pb.go │ │ │ │ ├── resolve.pb.go │ │ │ │ ├── resource.pb.go │ │ │ │ ├── role.pb.go │ │ │ │ ├── schema.pb.go │ │ │ │ ├── scim_token.pb.go │ │ │ │ ├── search.pb.go │ │ │ │ ├── studio.pb.go │ │ │ │ ├── studio_request.pb.go │ │ │ │ ├── token.pb.go │ │ │ │ ├── usage.gen.go │ │ │ │ ├── user.pb.go │ │ │ │ ├── verification_status.pb.go │ │ │ │ └── webhook.pb.go │ │ │ ├── studio/ │ │ │ │ └── v1alpha1/ │ │ │ │ ├── invoke.pb.go │ │ │ │ └── usage.gen.go │ │ │ └── webhook/ │ │ │ └── v1alpha1/ │ │ │ ├── event.pb.go │ │ │ └── usage.gen.go │ │ ├── google/ │ │ │ └── protobuf/ │ │ │ ├── cpp_features.pb.go │ │ │ ├── java_features.pb.go │ │ │ └── usage.gen.go │ │ └── grpc/ │ │ └── reflection/ │ │ └── v1/ │ │ ├── reflection.pb.go │ │ └── usage.gen.go │ └── pkg/ │ ├── bandeps/ │ │ ├── bandeps.go │ │ ├── checker.go │ │ ├── cmd/ │ │ │ └── bandeps/ │ │ │ ├── main.go │ │ │ └── usage.gen.go │ │ ├── key_rwlock.go │ │ ├── state.go │ │ ├── usage.gen.go │ │ ├── util.go │ │ └── violation.go │ ├── cache/ │ │ ├── cache.go │ │ └── usage.gen.go │ ├── cas/ │ │ ├── blob.go │ │ ├── blob_set.go │ │ ├── blob_set_test.go │ │ ├── blob_test.go │ │ ├── cas.go │ │ ├── digest.go │ │ ├── digest_test.go │ │ ├── errors.go │ │ ├── file_node.go │ │ ├── file_set.go │ │ ├── manifest.go │ │ ├── manifest_test.go │ │ ├── storage.go │ │ └── usage.gen.go │ ├── cert/ │ │ └── certclient/ │ │ ├── certclient.go │ │ ├── usage.gen.go │ │ └── util.go │ ├── connectclient/ │ │ ├── connectclient.go │ │ └── usage.gen.go │ ├── dag/ │ │ ├── comparable_graph.go │ │ ├── dag.go │ │ ├── dag_test.go │ │ ├── dagtest/ │ │ │ ├── dagtest.go │ │ │ └── usage.gen.go │ │ ├── errors.go │ │ ├── graph.go │ │ └── usage.gen.go │ ├── diff/ │ │ ├── diff.go │ │ ├── diffmyers/ │ │ │ ├── diffmyers.go │ │ │ ├── diffmyers_test.go │ │ │ ├── testdata/ │ │ │ │ ├── create │ │ │ │ ├── delete │ │ │ │ ├── delete-and-insert │ │ │ │ ├── equal │ │ │ │ ├── first-line-prefix │ │ │ │ ├── insert │ │ │ │ ├── lao-tzu │ │ │ │ └── remove │ │ │ └── usage.gen.go │ │ └── usage.gen.go │ ├── encoding/ │ │ ├── encoding.go │ │ ├── encoding_test.go │ │ └── usage.gen.go │ ├── filelock/ │ │ ├── filelock.go │ │ ├── filelock_test.go │ │ ├── lock.go │ │ ├── locker.go │ │ ├── nop_locker.go │ │ ├── nop_unlocker.go │ │ └── usage.gen.go │ ├── git/ │ │ ├── branch.go │ │ ├── cloner.go │ │ ├── cmd/ │ │ │ └── git-ls-files-unstaged/ │ │ │ ├── main.go │ │ │ └── usage.gen.go │ │ ├── git.go │ │ ├── git_test.go │ │ ├── lister.go │ │ ├── ref.go │ │ ├── ref_branch.go │ │ ├── remote.go │ │ ├── remote_test.go │ │ └── usage.gen.go │ ├── github/ │ │ └── githubtesting/ │ │ ├── archive_reader.go │ │ ├── githubtesting.go │ │ └── usage.gen.go │ ├── httpauth/ │ │ ├── env_authenticator.go │ │ ├── httpauth.go │ │ ├── multi_authenticator.go │ │ ├── netrc_authenticator.go │ │ ├── nop_authenticator.go │ │ ├── usage.gen.go │ │ └── util.go │ ├── indent/ │ │ ├── indent.go │ │ └── usage.gen.go │ ├── iotesting/ │ │ ├── iotesting.go │ │ └── usage.gen.go │ ├── licenseheader/ │ │ ├── cmd/ │ │ │ └── license-header/ │ │ │ ├── main.go │ │ │ └── usage.gen.go │ │ ├── licenseheader.go │ │ ├── licenseheader_test.go │ │ └── usage.gen.go │ ├── netext/ │ │ ├── netext.go │ │ ├── netext_test.go │ │ └── usage.gen.go │ ├── netrc/ │ │ ├── machine.go │ │ ├── netrc.go │ │ ├── netrc_unix.go │ │ ├── netrc_unix_test.go │ │ ├── netrc_windows.go │ │ ├── testdata/ │ │ │ └── unix/ │ │ │ ├── home1/ │ │ │ │ └── .netrc │ │ │ ├── home2/ │ │ │ │ └── .netrc │ │ │ └── home3/ │ │ │ └── .netrcc │ │ └── usage.gen.go │ ├── normalpath/ │ │ ├── normalpath.go │ │ ├── normalpath_test.go │ │ ├── normalpath_unix.go │ │ ├── normalpath_unix_test.go │ │ ├── normalpath_windows.go │ │ ├── normalpath_windows_test.go │ │ └── usage.gen.go │ ├── oauth2/ │ │ ├── client.go │ │ ├── client_test.go │ │ ├── device.go │ │ ├── oauth2.go │ │ └── usage.gen.go │ ├── osext/ │ │ ├── osext.go │ │ └── usage.gen.go │ ├── pluginrpcutil/ │ │ ├── pluginrpcutil.go │ │ ├── runner.go │ │ ├── usage.gen.go │ │ └── wasm_runner.go │ ├── protodescriptor/ │ │ ├── protodescriptor.go │ │ └── usage.gen.go │ ├── protoencoding/ │ │ ├── detrand.go │ │ ├── detrand_test.go │ │ ├── json_marshaler.go │ │ ├── json_unmarshaler.go │ │ ├── protoencoding.go │ │ ├── reparse_extensions.go │ │ ├── reparse_extensions_test.go │ │ ├── resolver.go │ │ ├── strip_legacy_options.go │ │ ├── strip_legacy_options_test.go │ │ ├── txtpb_marshaler.go │ │ ├── txtpb_unmarshaler.go │ │ ├── usage.gen.go │ │ ├── wire_marshaler.go │ │ ├── wire_unmarshaler.go │ │ ├── yaml_marshaler.go │ │ └── yaml_unmarshaler.go │ ├── protogenutil/ │ │ ├── named_helper.go │ │ ├── protogenutil.go │ │ └── usage.gen.go │ ├── protosourcepath/ │ │ ├── README.md │ │ ├── enum.go │ │ ├── enumvalue.go │ │ ├── field.go │ │ ├── message.go │ │ ├── method.go │ │ ├── protosourcepath.go │ │ ├── protosourcepath_test.go │ │ ├── service.go │ │ ├── testdata/ │ │ │ ├── proto2/ │ │ │ │ ├── import.proto │ │ │ │ └── test.proto │ │ │ └── proto3/ │ │ │ ├── import.proto │ │ │ └── test.proto │ │ └── usage.gen.go │ ├── protostat/ │ │ ├── protostat.go │ │ ├── protostat_test.go │ │ ├── protostatos/ │ │ │ ├── file_walker.go │ │ │ ├── protostatos.go │ │ │ └── usage.gen.go │ │ ├── protostatstorage/ │ │ │ ├── file_walker.go │ │ │ ├── protostatstorage.go │ │ │ └── usage.gen.go │ │ └── usage.gen.go │ ├── prototesting/ │ │ ├── prototesting.go │ │ ├── prototesting_unix.go │ │ ├── prototesting_windows.go │ │ └── usage.gen.go │ ├── prototime/ │ │ ├── prototime.go │ │ └── usage.gen.go │ ├── protoversion/ │ │ ├── package_version.go │ │ ├── protoversion.go │ │ ├── protoversion_test.go │ │ └── usage.gen.go │ ├── refcount/ │ │ ├── refcount.go │ │ ├── refcount_test.go │ │ └── usage.gen.go │ ├── shake256/ │ │ ├── shake256.go │ │ └── usage.gen.go │ ├── slogapp/ │ │ ├── console.go │ │ ├── console_test.go │ │ ├── slogapp.go │ │ ├── slogapp_test.go │ │ └── usage.gen.go │ ├── slogtestext/ │ │ ├── slogtestext.go │ │ └── usage.gen.go │ ├── storage/ │ │ ├── bucket.go │ │ ├── cmd/ │ │ │ ├── ddiff/ │ │ │ │ ├── main.go │ │ │ │ └── usage.gen.go │ │ │ └── storage-go-data/ │ │ │ ├── main.go │ │ │ └── usage.gen.go │ │ ├── copy.go │ │ ├── diff.go │ │ ├── errors.go │ │ ├── filter.go │ │ ├── limit.go │ │ ├── map.go │ │ ├── mapper.go │ │ ├── mask.go │ │ ├── mask_test.go │ │ ├── matcher.go │ │ ├── multi.go │ │ ├── storage.go │ │ ├── storagearchive/ │ │ │ ├── storagearchive.go │ │ │ └── usage.gen.go │ │ ├── storagemem/ │ │ │ ├── bucket.go │ │ │ ├── internal/ │ │ │ │ ├── internal.go │ │ │ │ └── usage.gen.go │ │ │ ├── read_object_closer.go │ │ │ ├── storagemem.go │ │ │ ├── storagemem_test.go │ │ │ ├── usage.gen.go │ │ │ └── write_object_closer.go │ │ ├── storageos/ │ │ │ ├── bucket.go │ │ │ ├── provider.go │ │ │ ├── storageos.go │ │ │ ├── storageos_test.go │ │ │ └── usage.gen.go │ │ ├── storagetesting/ │ │ │ ├── storagetesting.go │ │ │ ├── testdata/ │ │ │ │ ├── base/ │ │ │ │ │ ├── 1.proto │ │ │ │ │ ├── a/ │ │ │ │ │ │ ├── b/ │ │ │ │ │ │ │ ├── 1.proto │ │ │ │ │ │ │ ├── 2.proto │ │ │ │ │ │ │ └── 2.txt │ │ │ │ │ │ └── bar.yaml │ │ │ │ │ └── ab/ │ │ │ │ │ ├── 1.proto │ │ │ │ │ ├── 2.proto │ │ │ │ │ └── 2.txt │ │ │ │ ├── diff/ │ │ │ │ │ ├── a/ │ │ │ │ │ │ └── prefix/ │ │ │ │ │ │ ├── 1.txt │ │ │ │ │ │ └── 2.txt │ │ │ │ │ └── b/ │ │ │ │ │ └── prefix/ │ │ │ │ │ ├── 1.txt │ │ │ │ │ └── 3.txt │ │ │ │ ├── five/ │ │ │ │ │ ├── root1/ │ │ │ │ │ │ └── foo │ │ │ │ │ └── root2/ │ │ │ │ │ └── foo/ │ │ │ │ │ └── bar.proto │ │ │ │ ├── four/ │ │ │ │ │ └── root/ │ │ │ │ │ └── a/ │ │ │ │ │ ├── 3.proto │ │ │ │ │ └── b/ │ │ │ │ │ ├── 1.proto │ │ │ │ │ └── 2.proto │ │ │ │ ├── link/ │ │ │ │ │ └── file.proto │ │ │ │ ├── one/ │ │ │ │ │ └── root/ │ │ │ │ │ ├── 1.proto │ │ │ │ │ ├── a/ │ │ │ │ │ │ ├── 1.proto │ │ │ │ │ │ ├── 1.txt │ │ │ │ │ │ ├── b/ │ │ │ │ │ │ │ ├── 1.proto │ │ │ │ │ │ │ ├── 2.proto │ │ │ │ │ │ │ └── 2.txt │ │ │ │ │ │ └── bar.yaml │ │ │ │ │ ├── ab/ │ │ │ │ │ │ ├── 1.proto │ │ │ │ │ │ ├── 2.proto │ │ │ │ │ │ └── 2.txt │ │ │ │ │ ├── c/ │ │ │ │ │ │ └── 1.proto │ │ │ │ │ └── foo.yaml │ │ │ │ ├── overlay/ │ │ │ │ │ ├── a/ │ │ │ │ │ │ ├── 1 │ │ │ │ │ │ └── 2 │ │ │ │ │ └── b/ │ │ │ │ │ ├── 2 │ │ │ │ │ └── 3 │ │ │ │ ├── symlink_loop/ │ │ │ │ │ └── file.proto │ │ │ │ ├── symlink_success/ │ │ │ │ │ └── file.proto │ │ │ │ ├── three/ │ │ │ │ │ ├── a/ │ │ │ │ │ │ ├── one.proto │ │ │ │ │ │ └── one.txt │ │ │ │ │ └── b/ │ │ │ │ │ ├── one.txt │ │ │ │ │ └── two.proto │ │ │ │ └── two/ │ │ │ │ ├── foo.yaml │ │ │ │ ├── root1/ │ │ │ │ │ ├── 1.proto │ │ │ │ │ ├── a/ │ │ │ │ │ │ ├── 1.proto │ │ │ │ │ │ ├── 1.txt │ │ │ │ │ │ ├── b/ │ │ │ │ │ │ │ ├── 1.proto │ │ │ │ │ │ │ ├── 2.proto │ │ │ │ │ │ │ └── 2.txt │ │ │ │ │ │ └── bar.yaml │ │ │ │ │ ├── ab/ │ │ │ │ │ │ ├── 1.proto │ │ │ │ │ │ ├── 2.proto │ │ │ │ │ │ └── 2.txt │ │ │ │ │ ├── c/ │ │ │ │ │ │ └── 1.proto │ │ │ │ │ └── foo.yaml │ │ │ │ ├── root2/ │ │ │ │ │ ├── 2.proto │ │ │ │ │ ├── a/ │ │ │ │ │ │ ├── 2.proto │ │ │ │ │ │ ├── 2.txt │ │ │ │ │ │ ├── b/ │ │ │ │ │ │ │ ├── 3.proto │ │ │ │ │ │ │ ├── 4.proto │ │ │ │ │ │ │ └── 4.txt │ │ │ │ │ │ └── bat.yaml │ │ │ │ │ ├── ab/ │ │ │ │ │ │ ├── 3.proto │ │ │ │ │ │ ├── 4.proto │ │ │ │ │ │ └── 4.txt │ │ │ │ │ ├── baz.yaml │ │ │ │ │ └── c/ │ │ │ │ │ └── 3.proto │ │ │ │ └── rootoverlap/ │ │ │ │ ├── 1.proto │ │ │ │ ├── a/ │ │ │ │ │ ├── 1.proto │ │ │ │ │ ├── 1.txt │ │ │ │ │ ├── b/ │ │ │ │ │ │ ├── 1.proto │ │ │ │ │ │ ├── 2.proto │ │ │ │ │ │ └── 2.txt │ │ │ │ │ └── bar.yaml │ │ │ │ ├── ab/ │ │ │ │ │ ├── 1.proto │ │ │ │ │ ├── 2.proto │ │ │ │ │ └── 2.txt │ │ │ │ ├── c/ │ │ │ │ │ └── 1.proto │ │ │ │ └── foo.yaml │ │ │ └── usage.gen.go │ │ ├── storageutil/ │ │ │ ├── storageutil.go │ │ │ └── usage.gen.go │ │ ├── strip.go │ │ ├── usage.gen.go │ │ └── util.go │ ├── stringjson/ │ │ ├── stringjson.go │ │ └── usage.gen.go │ ├── syserror/ │ │ ├── syserror.go │ │ └── usage.gen.go │ ├── thread/ │ │ ├── thread.go │ │ ├── thread_test.go │ │ └── usage.gen.go │ ├── tmp/ │ │ ├── tmp.go │ │ ├── tmp_test.go │ │ └── usage.gen.go │ ├── transport/ │ │ └── http/ │ │ ├── httpclient/ │ │ │ ├── client.go │ │ │ ├── httpclient.go │ │ │ └── usage.gen.go │ │ └── httpserver/ │ │ ├── httpserver.go │ │ └── usage.gen.go │ ├── uuidutil/ │ │ ├── usage.gen.go │ │ ├── uuidutil.go │ │ └── uuidutil_test.go │ ├── verbose/ │ │ ├── usage.gen.go │ │ └── verbose.go │ └── wasm/ │ ├── compiled_module.go │ ├── runtime.go │ ├── unimplemented_runtime.go │ ├── usage.gen.go │ └── wasm.go └── proto/ └── buf/ └── alpha/ ├── audit/ │ └── v1alpha1/ │ ├── event.proto │ └── service.proto ├── breaking/ │ └── v1/ │ └── config.proto ├── image/ │ └── v1/ │ └── image.proto ├── lint/ │ └── v1/ │ └── config.proto ├── module/ │ └── v1alpha1/ │ └── module.proto ├── registry/ │ └── v1alpha1/ │ ├── admin.proto │ ├── authn.proto │ ├── authz.proto │ ├── convert.proto │ ├── display.proto │ ├── doc.proto │ ├── download.proto │ ├── git_metadata.proto │ ├── github.proto │ ├── image.proto │ ├── jsonschema.proto │ ├── module.proto │ ├── organization.proto │ ├── owner.proto │ ├── plugin_curation.proto │ ├── push.proto │ ├── reference.proto │ ├── repository.proto │ ├── repository_branch.proto │ ├── repository_commit.proto │ ├── repository_tag.proto │ ├── resolve.proto │ ├── resource.proto │ ├── role.proto │ ├── schema.proto │ ├── scim_token.proto │ ├── search.proto │ ├── studio.proto │ ├── studio_request.proto │ ├── token.proto │ ├── user.proto │ ├── verification_status.proto │ └── webhook.proto ├── studio/ │ └── v1alpha1/ │ └── invoke.proto └── webhook/ └── v1alpha1/ └── event.proto ================================================ FILE CONTENTS ================================================ ================================================ FILE: .bufstyle.yaml ================================================ version: v1 ignore: PACKAGE_FILENAME: - private/gen ================================================ FILE: .dockerignore ================================================ # Autogenerated by makego. DO NOT EDIT. .build/ .claude/settings.local.json .ctrlp .env/ .idea/ .tmp/ .vscode/ cmd/buf/buf cmd/buf/internal/command/alpha/protoc/internal/protoc-gen-insertion-point-receiver/protoc-gen-insertion-point-receiver cmd/buf/internal/command/alpha/protoc/internal/protoc-gen-insertion-point-writer/protoc-gen-insertion-point-writer cmd/buf/internal/command/alpha/protoc/test.txt cmd/buf/internal/command/generate/internal/protoc-gen-top-level-type-names-yaml/protoc-gen-top-level-type-names-yaml cmd/buf/testdata/imports/cache/v3/modulelocks/ cmd/buf/testdata/imports/corrupted_cache_dep/v3/modulelocks/ cmd/buf/testdata/imports/corrupted_cache_file/v3/modulelocks/ cmd/protoc-gen-buf-breaking/protoc-gen-buf-breaking cmd/protoc-gen-buf-lint/protoc-gen-buf-lint private/buf/buftesting/cache/ private/buf/bufwkt/cmd/wkt-go-data/wkt-go-data private/bufpkg/bufcheck/internal/cmd/buf-plugin-duplicate-category/buf-plugin-duplicate-category private/bufpkg/bufcheck/internal/cmd/buf-plugin-duplicate-rule/buf-plugin-duplicate-rule private/bufpkg/bufcheck/internal/cmd/buf-plugin-panic/buf-plugin-panic private/bufpkg/bufcheck/internal/cmd/buf-plugin-protovalidate-ext/buf-plugin-protovalidate-ext private/bufpkg/bufcheck/internal/cmd/buf-plugin-rpc-ext/buf-plugin-rpc-ext private/bufpkg/bufcheck/internal/cmd/buf-plugin-suffix/buf-plugin-suffix private/bufpkg/bufmodule/bufmoduleapi/cmd/buf-legacyfederation-go-data/buf-legacyfederation-go-data private/bufpkg/bufmodule/bufmoduletesting/cmd/buf-digest/buf-digest private/bufpkg/bufmodule/bufmoduletesting/cmd/buf-new-commit-id/buf-new-commit-id private/bufpkg/buftesting/cache/ private/pkg/bandeps/cmd/bandeps/bandeps private/pkg/git/cmd/git-ls-files-unstaged/git-ls-files-unstaged private/pkg/licenseheader/cmd/license-header/license-header private/pkg/storage/cmd/ddiff/ddiff private/pkg/storage/cmd/storage-go-data/storage-go-data private/pkg/storage/storageos/tmp/ ================================================ FILE: .envrc ================================================ . $(make -s direnv) ================================================ FILE: .gitattributes ================================================ # This is similar to the git option core.autocrlf but it applies to all # users of the repository and therefore doesn't depend on a developers # local configuration. * text=auto # Never attempt EOL conversion on files within testdata **/testdata/** -text -diff **/corpus/** linguist-generated=true **/usage.gen.go linguist-generated=true /private/gen/** linguist-generated=true ================================================ FILE: .github/CODEOWNERS ================================================ ================================================ FILE: .github/ISSUE_TEMPLATE/1-bug-report.yml ================================================ name: Bug Report description: File a bug report. labels: Bug body: - type: markdown attributes: value: | Thanks for helping us improve Buf by filing bugs, we really appreciate it! For us to investigate your issue efficiently, we need a minimal reproducible example. We get lots of support requests, so please help us help you. We find that the easiest way to do this is by linking to a GitHub repository with the setup of the example, as well as a set of commands for us to run on this GitHub repository to reproduce the issue. You can use an existing GitHub repository, or a temporary GitHub repository you create. **If you do not provide a minimal reproducible example in a GitHub repository, we will likely close your issue until a reproducible example is provided. We apologize, but we have to be efficient with our support requests, and we appreciate your help.** [This article](https://stackoverflow.com/help/minimal-reproducible-example) on minimal reproducible examples may be of use! - type: input id: github-repository attributes: label: GitHub repository with your minimal reproducible example (do not leave this field blank or fill out this field with "github.com/bufbuild/buf" or we will automatically close your issue, see the instructions above!) description: Provide us with a link to the GitHub repository that contains the setup needed to reproduce the issue. This is a repository you create, and is required for us to investigate your issue. placeholder: https://github.com/you/temp-repo-with-repro validations: required: true - type: textarea id: commands attributes: label: Commands description: Please provide the commands to run from the root of this repository to reproduce the issue. placeholder: | cd ./proto buf lint render: shell validations: required: true - type: textarea id: actual-output attributes: label: Output description: What is the output of running these commands? placeholder: | foo/bar/v1/bar.proto:37:29:Field name "CreateTime" should be lower_snake_case, such as "create_time". render: shell validations: required: true - type: textarea id: expected-output attributes: label: Expected Output description: What did you expect instead? placeholder: I expected no lint failures. validations: required: true - type: textarea id: other attributes: label: Anything else? description: Is there any other context you'd like us to have? ================================================ FILE: .github/ISSUE_TEMPLATE/2-feature-request.yml ================================================ name: Feature Request description: Let us know about a feature you'd like us to add. labels: Feature body: - type: markdown attributes: value: | Thanks for your suggestions on how to improve Buf, we really appreciate it! - type: textarea id: feature attributes: label: Feature description: Tell us what you'd like to see! validations: required: true ================================================ FILE: .github/ISSUE_TEMPLATE/3-other.yml ================================================ name: Other description: Anything else. body: - type: markdown attributes: value: | Thanks for getting in touch, we really appreciate it! If your issue isn't a bug or feature request, we'd highly recommend to talk to us on the [Buf Slack](https://buf.build/links/slack). If you think the issue is more appropriate for a GitHub issue, please file it below! If this is a question about some behavior you are witnessing, we will need a minimal reproducible example. We get lots of support requests, so please help us help you. We find that the easiest way to do this is by linking to a GitHub repository with the setup of the example, as well as a set of commands for us to run on this GitHub repository to reproduce the issue. You can use an existing GitHub repository, or a temporary GitHub repository you create. **If this is a question about some behavior you are witnessing, and you do not provide a minimal reproducible example in a GitHub repository, we will likely close your issue until a reproducible example is provided. We apologize, but we have to be efficient with our support requests, and we appreciate your help.** [This article](https://stackoverflow.com/help/minimal-reproducible-example) on minimal reproducible examples may be of use! - type: input id: github-repository attributes: label: GitHub repository with your minimal reproducible example (do not leave this field blank or fill out this field with "github.com/bufbuild/buf" or we will automatically close your issue, see the instructions above!) description: If this is a question about some behavior you are witnessing, provide us with a link to the GitHub repository that contains the setup needed to reproduce the issue. This is a repository you create, and is required for us to investigate your issue. placeholder: https://github.com/you/temp-repo-with-repro - type: textarea id: feature attributes: label: What's up? description: Tell us what's on your mind! validations: required: true ================================================ FILE: .github/dependabot.yml ================================================ version: 2 updates: - package-ecosystem: "docker" directory: "/" schedule: interval: "weekly" - package-ecosystem: "github-actions" directory: "/" schedule: interval: "weekly" - package-ecosystem: "gomod" directory: "/" schedule: interval: "weekly" ignore: # We manually update this from main and lock the commit in make/buf/all.mk - dependency-name: "github.com/bufbuild/protocompile" # This needs to be locked to the same version we generate with, which is # controlled via dep_protoc_gen_connect_go.mk - dependency-name: "github.com/bufbuild/connect-go" # This needs to be locked to the same version we generate with, which is # controlled via make/go/dep_protoc_gen_go.mk - dependency-name: "google.golang.org/protobuf" ================================================ FILE: .github/workflows/add-to-project.yaml ================================================ name: Add issues and PRs to project on: issues: types: - opened - reopened - transferred pull_request_target: types: - opened - reopened issue_comment: types: - created jobs: call-workflow-add-to-project: name: Call workflow to add issue to project uses: bufbuild/base-workflows/.github/workflows/add-to-project.yaml@main secrets: inherit ================================================ FILE: .github/workflows/back-to-development.yaml ================================================ name: Go back to Development on: workflow_dispatch: release: types: [published] env: APP_ID: 251311 jobs: post-release: runs-on: ubuntu-latest steps: - name: Get GitHub app token uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v3.0.0 id: app_token with: app-id: ${{ env.APP_ID }} private-key: ${{ secrets.TOKEN_EXCHANGE_GH_APP_PRIVATE_KEY }} - name: Checkout repository code uses: actions/checkout@v6 with: token: ${{ steps.app_token.outputs.token }} - name: Set up Git name and email run: | git config user.name "${{ github.actor }}" git config user.email "${{ github.actor }}@users.noreply.github.com" - name: Create PR back to development run: bash ./make/buf/scripts/gobacktodevelopment.bash env: GH_TOKEN: ${{ steps.app_token.outputs.token }} WEBHOOK_URL: ${{ secrets.SLACK_RELEASE_NOTIFICATION_WEBHOOK }} ================================================ FILE: .github/workflows/buf-binary-size.yaml ================================================ name: binary-size on: push: branches: - main tags: ["v*"] pull_request: # Prevent writing to the repository using the CI token. # Ref: https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#permissions permissions: read-all env: MAKEFLAGS: "-j 2" jobs: buf-binary-size: runs-on: ubuntu-latest steps: - name: checkout uses: actions/checkout@v6 - name: setup-go uses: actions/setup-go@v6 with: go-version: "1.26.x" check-latest: true - name: cache uses: actions/cache@v5 with: path: | ~/.cache/buf/${{ runner.os }}/x86_64/bin ~/.cache/buf/${{ runner.os }}/x86_64/go/pkg/mod ~/.cache/buf/${{ runner.os }}/x86_64/gocache ~/.cache/buf/${{ runner.os }}/x86_64/include ~/.cache/buf/${{ runner.os }}/x86_64/versions key: ${{ runner.os }}-buf-${{ hashFiles('**/go.sum', 'make/**') }} restore-keys: | ${{ runner.os }}-buf- - name: make-bufbinarysize run: make bufbinarysize ================================================ FILE: .github/workflows/buf-ci.yaml ================================================ name: Buf CI on: push: pull_request: types: [opened, synchronize, reopened, labeled, unlabeled] delete: permissions: contents: read pull-requests: write jobs: buf: runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - uses: bufbuild/buf-action@v1 with: token: ${{ secrets.BUF_TOKEN }} ================================================ FILE: .github/workflows/build-and-draft-release.yaml ================================================ name: Build and Draft Release on: pull_request: types: [closed] workflow_dispatch: inputs: version: type: string description: The released version without 'v'. For example, 1.0.0. env: APP_ID: 251311 jobs: draft_release: runs-on: ubuntu-latest if: ${{ github.event_name == 'workflow_dispatch' || (github.event_name == 'pull_request' && github.event.pull_request.merged == true && startsWith(github.event.pull_request.head.ref, 'release')) }} steps: - name: Set VERSION variable # The head ref looks like release/v1.0.0, and we need to trim the string up to the `/v`. run: | VERSION="${{ github.event.inputs.version || github.head_ref}}" echo "VERSION=${VERSION##*/v}" >> $GITHUB_ENV - name: Get GitHub app token uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v3.0.0 id: app_token with: app-id: ${{ env.APP_ID }} private-key: ${{ secrets.TOKEN_EXCHANGE_GH_APP_PRIVATE_KEY }} - name: Checkout repository code uses: actions/checkout@v6 with: token: ${{ steps.app_token.outputs.token }} - name: Set up Go uses: actions/setup-go@v6 with: go-version: "1.26.x" check-latest: true - name: Set up Git name and email run: | git config user.name "${{ github.actor }}" git config user.email "${{ github.actor }}@users.noreply.github.com" - name: Build assets and draft release run: bash ./make/buf/scripts/draftrelease.bash env: GH_TOKEN: ${{ steps.app_token.outputs.token }} WEBHOOK_URL: ${{ secrets.SLACK_RELEASE_NOTIFICATION_WEBHOOK }} RELEASE_MINISIGN_PRIVATE_KEY: ${{secrets.RELEASE_MINISIGN_PRIVATE_KEY}} RELEASE_MINISIGN_PRIVATE_KEY_PASSWORD: ${{secrets.RELEASE_MINISIGN_PRIVATE_KEY_PASSWORD}} - name: Unset keys if: ${{ always() }} run: | unset RELEASE_MINISIGN_PRIVATE_KEY unset RELEASE_MINISIGN_PRIVATE_KEY_PASSWORD ================================================ FILE: .github/workflows/ci.yaml ================================================ name: ci on: push: branches: - main tags: ["v*"] pull_request: # Prevent writing to the repository using the CI token. # Ref: https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#permissions permissions: read-all env: MAKEFLAGS: "-j 2" jobs: lint: runs-on: ubuntu-latest steps: - name: checkout uses: actions/checkout@v6 - name: setup-go uses: actions/setup-go@v6 with: go-version: "1.26.x" check-latest: true - name: cache uses: actions/cache@v5 with: path: | ~/.cache/buf/${{ runner.os }}/x86_64/bin ~/.cache/buf/${{ runner.os }}/x86_64/go/pkg/mod ~/.cache/buf/${{ runner.os }}/x86_64/gocache ~/.cache/buf/${{ runner.os }}/x86_64/include ~/.cache/buf/${{ runner.os }}/x86_64/versions key: ${{ runner.os }}-buf-lint-${{ hashFiles('**/go.sum', 'make/**') }} restore-keys: | ${{ runner.os }}-buf-lint- - name: golangci-lint-cache uses: actions/cache@v5 with: path: ~/.cache/golangci-lint # https://github.com/golangci/golangci-lint-action#caching-internals includes an interval number in the cache # key, however we update our go modules at least once weekly so we shouldn't need that. key: ${{ runner.os }}-buf-golangci-lint-${{ hashFiles('**/go.sum') }} restore-keys: | ${{ runner.os }}-buf-golangci-lint- - name: make-lint run: make lint env: BUF_BREAKING_AGAINST_INPUT: "https://github.com/bufbuild/buf.git#branch=main" BUF_INPUT_HTTPS_USERNAME: ${{ github.actor }} BUF_INPUT_HTTPS_PASSWORD: ${{ github.token }} test: runs-on: ubuntu-latest steps: - name: checkout uses: actions/checkout@v6 - name: setup-go uses: actions/setup-go@v6 with: go-version: "1.26.x" check-latest: true - name: cache uses: actions/cache@v5 with: path: | ~/.cache/buf/${{ runner.os }}/x86_64/bin ~/.cache/buf/${{ runner.os }}/x86_64/go/pkg/mod ~/.cache/buf/${{ runner.os }}/x86_64/gocache ~/.cache/buf/${{ runner.os }}/x86_64/include ~/.cache/buf/${{ runner.os }}/x86_64/versions key: ${{ runner.os }}-buf-test-${{ hashFiles('**/go.sum', 'make/**') }} restore-keys: | ${{ runner.os }}-buf-test- - name: make-test run: make test docker: runs-on: ubuntu-latest needs: - lint - test # This job only runs when # 1. The previous lint and test jobs have completed successfully # 2. The repository is not a fork, i.e. it will only run on the official bufbuild/buf # 3. The workflow run is trigged by main branch OR a tag with v prefix # See https://github.com/bufbuild/buf/pull/289/files#r596207623 for the discussion if: ${{ github.repository == 'bufbuild/buf' && (github.ref == 'refs/heads/main' || contains(github.ref, 'refs/tags/v')) }} steps: # qemu is used when executing things like `apk` in the final build # stage which must execute on the target platform. We currently do # not have any CGO and care should be taken in the Dockerfile to ensure # that go cross compilation happens on the build platform. - name: setup-qemu uses: docker/setup-qemu-action@v4 id: qemu with: # alpine image doesn't support linux/riscv64 platforms: linux/386,linux/amd64,linux/arm64,linux/arm/v7,linux/arm/v6,linux/ppc64le,linux/s390x - name: setup-docker-buildx uses: docker/setup-buildx-action@v4 - name: login-docker uses: docker/login-action@v4 with: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_TOKEN }} - name: docker-meta id: meta uses: docker/metadata-action@v6 with: images: | bufbuild/buf tags: | type=edge,branch=main type=semver,pattern={{version}} type=semver,pattern={{major}}.{{minor}} type=semver,pattern={{major}} flavor: | latest=auto - name: docker-build-push uses: docker/build-push-action@v7 with: file: Dockerfile.buf platforms: ${{ steps.qemu.outputs.platforms }} push: true tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} ================================================ FILE: .github/workflows/codeql.yaml ================================================ # Broadly based on example Github action from # https://github.com/github/codeql-action#usage name: codeql # https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#permissions # https://github.com/github/codeql-action/issues/572 permissions: actions: read contents: read pull-requests: read security-events: write on: push: branches: - main jobs: codeql: runs-on: ubuntu-latest steps: - name: checkout uses: actions/checkout@v6 - name: setup-go uses: actions/setup-go@v6 with: go-version: "1.26.x" check-latest: true - name: initialize uses: github/codeql-action/init@v4 with: # Limit analysis to Go for now. # Available languages: https://docs.github.com/en/code-security/secure-coding/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#changing-the-languages-that-are-analyzed languages: go - name: autobuild uses: github/codeql-action/autobuild@v4 - name: analyze uses: github/codeql-action/analyze@v4 ================================================ FILE: .github/workflows/create-release-pr.yaml ================================================ name: Create Release PR on: workflow_dispatch: inputs: version: type: string description: The released version without 'v'. For example, 1.0.0. env: APP_ID: 251311 jobs: prepare: runs-on: ubuntu-latest steps: - name: Get GitHub app token uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v3.0.0 id: app_token with: app-id: ${{ env.APP_ID }} private-key: ${{ secrets.TOKEN_EXCHANGE_GH_APP_PRIVATE_KEY }} - name: Checkout repository code uses: actions/checkout@v6 with: token: ${{ steps.app_token.outputs.token }} - name: Set up Git name and email run: | git config user.name "${{ github.actor }}" git config user.email "${{ github.actor }}@users.noreply.github.com" - name: Create release PR run: bash ./make/buf/scripts/createreleasepr.bash env: GH_TOKEN: ${{ steps.app_token.outputs.token }} VERSION: ${{ github.event.inputs.version }} WEBHOOK_URL: ${{ secrets.SLACK_RELEASE_NOTIFICATION_WEBHOOK }} ================================================ FILE: .github/workflows/docker-publish.yaml ================================================ # This job will build and push the Docker image to Docker Hub. name: docker-publish on: workflow_dispatch: inputs: version: type: string required: true description: The release version (e.g., X.Y.Z), without the v. latest: type: boolean description: Tag with latest default: false publish: type: boolean description: Publish the image to Docker Hub default: false permissions: read-all jobs: docker: runs-on: ubuntu-latest if: github.repository == 'bufbuild/buf' steps: - name: Validate SemVer run: | VERSION=${{ github.event.inputs.version }} SEMVER_REGEX='^[0-9]*\.[0-9]*\.[0-9]*$' if [[ ! $VERSION =~ $SEMVER_REGEX ]]; then echo "Error: '$VERSION' is not a valid SemVer version" exit 1 else echo "Version '$VERSION' is a valid SemVer version" fi - name: checkout id: checkout uses: actions/checkout@v6 with: ref: refs/tags/v${{ github.event.inputs.version }} - name: setup-qemu uses: docker/setup-qemu-action@v4 id: qemu with: platforms: linux/386,linux/amd64,linux/arm64,linux/arm/v7,linux/arm/v6,linux/ppc64le,linux/s390x - name: setup-docker-buildx uses: docker/setup-buildx-action@v4 - name: login-docker uses: docker/login-action@v4 with: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_TOKEN }} - name: docker-meta id: meta uses: docker/metadata-action@v6 with: images: | bufbuild/buf tags: | type=raw,value=latest,enable=${{ github.event.inputs.latest }} type=semver,pattern={{major}},value=v${{ github.event.inputs.version }} type=semver,pattern={{major}}.{{minor}},value=v${{ github.event.inputs.version }} type=semver,pattern={{major}}.{{minor}}.{{patch}},value=v${{ github.event.inputs.version }} type=semver,pattern={{version}},value=v${{ github.event.inputs.version }} labels: | org.opencontainers.image.revision=${{ steps.checkout.outputs.commit }} org.opencontainers.image.version=${{ github.event.inputs.version }} flavor: | latest=false - name: docker-build uses: docker/build-push-action@v7 with: context: . # This uses the checkout context file: Dockerfile.buf load: true push: false tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} - name: docker-test run: | OUTPUT=$(docker run --rm bufbuild/buf:${{ github.event.inputs.version }} --version) if [ "$OUTPUT" == "${{ github.event.inputs.version }}" ]; then echo "Version matches" else echo "Version does not match: expected ${{ github.event.inputs.version }}, got $OUTPUT" exit 1 fi - name: docker-build-publish uses: docker/build-push-action@v7 if: ${{ github.event.inputs.publish == 'true' }} with: context: . # This uses the checkout context file: Dockerfile.buf platforms: ${{ steps.qemu.outputs.platforms }} push: ${{ github.event.inputs.publish == 'true' }} tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} ================================================ FILE: .github/workflows/emergency-review-bypass.yaml ================================================ name: Bypass review in case of emergency on: pull_request: types: - labeled permissions: pull-requests: write jobs: approve: if: github.event.label.name == 'Emergency Bypass Review' uses: bufbuild/base-workflows/.github/workflows/emergency-review-bypass.yaml@main secrets: inherit ================================================ FILE: .github/workflows/make-upgrade.yaml ================================================ name: Make upgrade on: schedule: # Run every Monday at 10am UTC - cron: "0 10 * * 1" env: APP_ID: 251311 jobs: prepare: runs-on: ubuntu-latest steps: - name: Get GitHub app token uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v3.0.0 id: app_token with: app-id: ${{ env.APP_ID }} private-key: ${{ secrets.TOKEN_EXCHANGE_GH_APP_PRIVATE_KEY }} - name: Checkout repository code uses: actions/checkout@v6 with: token: ${{ steps.app_token.outputs.token }} - name: setup-go uses: actions/setup-go@v6 with: go-version: "1.26.x" check-latest: true cache: false - name: Set up Git name and email run: | git config user.name 'github-actions[bot]' git config user.email 'github-actions[bot]@users.noreply.github.com' - name: make-githubactionupgrade run: make githubactionupgrade env: GH_TOKEN: ${{ steps.app_token.outputs.token }} ================================================ FILE: .github/workflows/notify-approval-bypass.yaml ================================================ name: PR Approval Bypass Notifier on: pull_request: types: - closed branches: - main permissions: pull-requests: read jobs: approval: uses: bufbuild/base-workflows/.github/workflows/notify-approval-bypass.yaml@main secrets: inherit ================================================ FILE: .github/workflows/pr-title.yaml ================================================ name: Lint PR Title # Prevent writing to the repository using the CI token. # Ref: https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#permissions permissions: pull-requests: read on: pull_request: # By default, a workflow only runs when a pull_request's activity type is opened, # synchronize, or reopened. We explicity override here so that PR titles are # re-linted when the PR text content is edited. types: - opened - edited - reopened - synchronize jobs: lint: uses: bufbuild/base-workflows/.github/workflows/pr-title.yaml@main ================================================ FILE: .github/workflows/previous.yaml ================================================ name: previous on: push: branches: - main tags: ["v*"] pull_request: # Prevent writing to the repository using the CI token. # Ref: https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#permissions permissions: read-all env: MAKEFLAGS: "-j 2" jobs: test-previous: strategy: matrix: go-version: ["1.25.x"] runs-on: ubuntu-latest steps: - name: checkout uses: actions/checkout@v6 - name: setup-go uses: actions/setup-go@v6 with: go-version: ${{ matrix.go-version }} check-latest: true - name: cache uses: actions/cache@v5 with: path: | ~/.cache/buf/${{ runner.os }}/x86_64/bin ~/.cache/buf/${{ runner.os }}/x86_64/go/pkg/mod ~/.cache/buf/${{ runner.os }}/x86_64/gocache ~/.cache/buf/${{ runner.os }}/x86_64/include ~/.cache/buf/${{ runner.os }}/x86_64/versions key: ${{ runner.os }}-${{ matrix.go-version }}-buf-${{ hashFiles('**/go.sum', 'make/**') }} restore-keys: | ${{ runner.os }}-${{ matrix.go-version }}-buf- - name: make-test run: make test ================================================ FILE: .github/workflows/verify-changelog.yaml ================================================ name: Verify Changelog on: pull_request: types: - opened push: branches: - "release/**" - "next/**" jobs: verify: runs-on: ubuntu-latest if: ${{ github.event_name == 'push' || startsWith(github.head_ref, 'release/') || startsWith(github.head_ref, 'next/')}} steps: - name: Checkout repository code uses: actions/checkout@v6 with: token: ${{ secrets.GITHUB_TOKEN }} fetch-depth: 0 - name: Check changelog is modified run: bash ./make/buf/scripts/verifychangelog.bash ================================================ FILE: .github/workflows/windows.yaml ================================================ name: windows on: push: branches: - main tags: ["v*"] pull_request: # Prevent writing to the repository using the CI token. # Ref: https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#permissions permissions: read-all jobs: test: env: DOWNLOAD_CACHE: 'd:\downloadcache' # Improve performance by using D: drive. # C: seems to be really slow, especially for cache restores. GOPATH: 'd:\go\path' GOCACHE: 'd:\go\cache' GOMODCACHE: 'd:\go\modcache' runs-on: windows-latest steps: - name: support-longpaths run: git config --system core.longpaths true - name: checkout uses: actions/checkout@v6 - name: setup-go uses: actions/setup-go@v6 with: go-version: "1.26.x" check-latest: true cache: true - name: windows-cache uses: actions/cache@v5 with: path: | ${{ env.DOWNLOAD_CACHE }} key: ${{ runner.os }}-buf-windows-${{ hashFiles('windows/**') }} restore-keys: | ${{ runner.os }}-buf-windows- - name: test shell: bash run: ./etc/windows/test.bash ================================================ FILE: .gitignore ================================================ # Autogenerated by makego. DO NOT EDIT. /.build/ /.claude/settings.local.json /.ctrlp /.env/ /.idea/ /.tmp/ /.vscode/ /cmd/buf/buf /cmd/buf/internal/command/alpha/protoc/internal/protoc-gen-insertion-point-receiver/protoc-gen-insertion-point-receiver /cmd/buf/internal/command/alpha/protoc/internal/protoc-gen-insertion-point-writer/protoc-gen-insertion-point-writer /cmd/buf/internal/command/alpha/protoc/test.txt /cmd/buf/internal/command/generate/internal/protoc-gen-top-level-type-names-yaml/protoc-gen-top-level-type-names-yaml /cmd/buf/testdata/imports/cache/v3/modulelocks/ /cmd/buf/testdata/imports/corrupted_cache_dep/v3/modulelocks/ /cmd/buf/testdata/imports/corrupted_cache_file/v3/modulelocks/ /cmd/protoc-gen-buf-breaking/protoc-gen-buf-breaking /cmd/protoc-gen-buf-lint/protoc-gen-buf-lint /private/buf/buftesting/cache/ /private/buf/bufwkt/cmd/wkt-go-data/wkt-go-data /private/bufpkg/bufcheck/internal/cmd/buf-plugin-duplicate-category/buf-plugin-duplicate-category /private/bufpkg/bufcheck/internal/cmd/buf-plugin-duplicate-rule/buf-plugin-duplicate-rule /private/bufpkg/bufcheck/internal/cmd/buf-plugin-panic/buf-plugin-panic /private/bufpkg/bufcheck/internal/cmd/buf-plugin-protovalidate-ext/buf-plugin-protovalidate-ext /private/bufpkg/bufcheck/internal/cmd/buf-plugin-rpc-ext/buf-plugin-rpc-ext /private/bufpkg/bufcheck/internal/cmd/buf-plugin-suffix/buf-plugin-suffix /private/bufpkg/bufmodule/bufmoduleapi/cmd/buf-legacyfederation-go-data/buf-legacyfederation-go-data /private/bufpkg/bufmodule/bufmoduletesting/cmd/buf-digest/buf-digest /private/bufpkg/bufmodule/bufmoduletesting/cmd/buf-new-commit-id/buf-new-commit-id /private/bufpkg/buftesting/cache/ /private/pkg/bandeps/cmd/bandeps/bandeps /private/pkg/git/cmd/git-ls-files-unstaged/git-ls-files-unstaged /private/pkg/licenseheader/cmd/license-header/license-header /private/pkg/storage/cmd/ddiff/ddiff /private/pkg/storage/cmd/storage-go-data/storage-go-data /private/pkg/storage/storageos/tmp/ ================================================ FILE: .godoclint.yaml ================================================ # Configuration file version. version: "1.0" # List of regexp patterns matching files the linter should include. When # omitted/null, the linter includes all Go files. If assigned then only the # files that their relative path (with respect to the config file path) matches # any of the patterns will be processed. # # Note: The patterns must assume a Unix-like path (i.e., separated with forward # slashes, `/`), even on Windows. This is to ensure a consistent behavior across # different platforms. # # Example: # include: # - ^pkg/ # - _foo.go$ include: null # List of regexp patterns matching files the linter should skip. When # omitted/null, the linter excludes no Go files. If assigned then only the # files that their relative path (with respect to the config file path) does not # match any of the patterns will be processed. # # Note: The patterns must assume a Unix-like path (i.e., separated with forward # slashes, `/`), even on Windows. This is to ensure a consistent behavior across # different platforms. # # Example: # exclude: # - ^internal/ # - _autogenerated.go$ exclude: - ^private/gen # Default set of rules to enable. Possible values are: # - `basic`: enables basic rules which are: `pkg-doc`, `single-pkg-doc`, `start-with-name` and `deprecated`. # - `all`: all rules are enabled by default; the `disable` key can be used to disable specific rules. # - `none`: no rule is enabled by default; the `enable` key can be used to enable specific rules. default: none # List of rules to enable *in addition to* the default set. # # See the linter docs for more on supported rules. # # Example: # - pkg-doc # - single-pkg-doc # - require-pkg-doc # - specific-file-pkg-doc # - start-with-name # - require-doc # - deprecated # - max-len # - no-unused-link enable: - pkg-doc - single-pkg-doc #- require-pkg-doc - specific-file-pkg-doc - start-with-name - require-doc - deprecated #- max-len - no-unused-link # List of rules to disable. # # Example: # disable: # - pkg-doc # - single-pkg-doc disable: null # A map for setting individual rule options. # # All sub-keys are optional. options: # Maximum line length for godocs, not including the `// `, or `/*` or `*/` # tokens. max-len/length: 120 # Include test files when applying the `max-len` rule. max-len/include-tests: false # Include test files when applying the `pkg-doc` rule. pkg-doc/include-tests: false # Include test files when applying the `single-pkg-doc` rule. single-pkg-doc/include-tests: false # Include test files when applying the `require-pkg-doc` rule. require-pkg-doc/include-tests: false # Include test files when applying the `specific-file-pkg-doc` rule. specific-file-pkg-doc/include-tests: false # The file pattern to indicate which file should contain the package-level godoc when # applying the `specific-file-pkg-doc` rule. # # Valid values are: # # - "doc": The godoc should be in a file named doc.go # - "package-name": The godoc should be in a file named after the package. For example, if a package # is named "foobar", the godoc should be in a file named "foobar.go". specific-file-pkg-doc/file-pattern: "package-name" # Include test files when applying the `require-doc` rule. require-doc/include-tests: false # Ignore exported (public) symbols when applying the `require-doc` rule. require-doc/ignore-exported: false # Ignore unexported (private) symbols when applying the `require-doc` rule. require-doc/ignore-unexported: true # Include test files when applying the `start-with-name` rule. start-with-name/include-tests: false # Include unexported (private) symbols when applying the `start-with-name` rule. start-with-name/include-unexported: false # Include test files when applying the `no-unused-link` rule. no-unused-link/include-tests: false ================================================ FILE: .golangci.yml ================================================ version: "2" linters: default: none enable: - asciicheck - bidichk - bodyclose - containedctx - copyloopvar # - contextcheck - decorder # - depguard - dogsled - errcheck - exhaustruct - forbidigo - forcetypeassert - gochecknoinits - goheader - gomodguard - goprintffuncname - gosec - govet - grouper - importas - ineffassign - loggercheck - makezero - mirror - misspell - modernize - nakedret - nilerr - nolintlint - nosprintfhostport - paralleltest - predeclared - promlinter - reassign - rowserrcheck - staticcheck - unconvert - unused - usetesting - wastedassign - whitespace settings: errcheck: check-type-assertions: true forbidigo: forbid: # Use private/pkg/thread.Parallelize - pattern: ^errgroup\. # Use private/pkg/standard/xos/xexec - pattern: ^exec\.Cmd$ - pattern: ^exec\.Command$ - pattern: ^exec\.CommandContext$ # os.Rename does not work across filesystem boundaries # See https://github.com/bufbuild/buf/issues/639 - pattern: ^os\.Rename$ # Use private/pkg/osext.Getwd - pattern: ^os\.Getwd$ # Use private/pkg/osext.Chdir - pattern: ^os\.Chdir$ # Ban debug statements - pattern: ^fmt\.Print - pattern: ^log\. - pattern: ^print$ - pattern: ^println$ # Use private/pkg/protoencoding Marshalers and Unmarshalers - pattern: ^(proto|prototext|protojson|protoyaml).Marshal$ - pattern: ^(proto|prototext|protojson|protoyaml).Unmarshal$ - pattern: ^(proto|prototext|protojson|protoyaml).MarshalOptions$ - pattern: ^(proto|prototext|protojson|protoyaml).UnmarshalOptions$ - pattern: ^proto\.Clone$ msg: please use proto.CloneOf govet: enable: - nilness importas: alias: - pkg: github.com/bufbuild/buf/private/gen/proto/go/buf/alpha/image/v1 alias: imagev1 - pkg: github.com/bufbuild/buf/private/gen/proto/go/buf/alpha/module/v1 alias: modulev1 - pkg: github.com/bufbuild/buf/private/gen/proto/go/buf/alpha/registry/v1alpha1 alias: registryv1alpha1 nolintlint: require-explanation: true require-specific: true allow-unused: false usetesting: context-background: true context-todo: true exclusions: generated: lax presets: - comments - common-false-positives - legacy - std-error-handling rules: - linters: - staticcheck # No need to require embedded fields from selector expression, they could help with # more expressive code. text: "QF1008" - linters: - staticcheck # Whether or not to apply De Morgan's law is contextual and should be handled during # code review. text: "QF1001" - linters: - paralleltest # We use chdir in these tests. path: cmd/buf/workspace_subdir_test.go text: missing the call to method parallel - linters: - paralleltest # TestWorkspaceArchiveDir, TestWorkspaceWithInvalidArchivePathFail # and TestWorkspaceWithInvalidArchiveAbsolutePathFail cannot run in parallel # because they all call createZipFromDir on the same path, writing to the same file. path: cmd/buf/workspace_test.go text: missing the call to method parallel - linters: - paralleltest # This test shouldn't run in parallel as it needs osext.Getwd. path: cmd/buf/internal/command/config/configmigrate/configmigrate_test.go text: missing the call to method parallel - linters: - forbidigo # This is a legacy usage of os.Getwd we're not bothering to port yet. path: cmd/buf/internal/command/alpha/protoc/protoc_test.go text: os.Getwd - linters: - dogsled # One function call doesn't care about most of the returned destructured values. The # dogsled linter complains about it. (Value of this linter is unclear...) path: cmd/buf/internal/command/curl/curl.go - linters: - forbidigo # This is a legacy usage of os.Getwd we're not bothering to port yet. path: cmd/buf/internal/command/generate/generate_test.go text: os.Getwd - linters: - exhaustruct # We didn't turn on exhaustruct historically, but we really want to make sure it is turned on # for this file, as we do conversion between v1beta1 and v1 registry-proto types. path-except: private/bufpkg/bufmodule/bufmoduleapi/convert.go - linters: - gosec # G101 checks for hardcoded credentials, and the variables named "*Password* # trip this off. path: private/buf/bufcli/env.go text: "G101:" - linters: - gosec # G404 checks for use of the ordinary non-CPRNG. path: private/buf/buflsp/progress.go text: "G404:" - linters: - gosec # G115 checks for use of truncating conversions. path: private/buf/buflsp/file.go text: "G115:" - linters: - gosec # G115 checks for use of truncating conversions. path: private/buf/buflsp/diagnostic.go text: "G115:" - linters: - gosec # G115 checks for use of truncating conversions. path: private/buf/buflsp/image.go text: "G115:" - linters: - gosec # G115 checks for use of truncating conversions. path: private/buf/buflsp/report.go text: "G115:" - linters: - gosec # G115 checks for use of truncating conversions. path: private/buf/buflsp/server.go text: "G115:" - linters: - gosec # G115 checks for use of truncating conversions. path: private/buf/buflsp/symbol.go text: "G115:" - linters: - gosec # G115 checks for use of truncating conversions. path: private/buf/buflsp/semantic_tokens.go text: "G115:" - linters: - gosec # G115 checks for use of truncating conversions. path: private/buf/buflsp/cel.go text: "G115:" - linters: - gosec # G115 checks for use of truncating conversions. path: private/buf/buflsp/folding_range.go text: "G115:" - linters: - gosec # G115 checks for use of truncating conversions. path: private/buf/buflsp/completion.go text: "G115:" - linters: - gosec # G115 checks for use of truncating conversions. path: private/buf/buflsp/organize_imports.go text: "G115:" - linters: - containedctx # Type must implement an interface whose methods do not accept context. But this # implementation makes RPC calls, which need a context. So we allow creator of the # type to provide a context at value creation (instead of using context.Background()). path: private/buf/bufcurl/reflection_resolver.go - linters: - gosec # We verify manually so that we can emit verbose output while doing so. path: private/buf/bufcurl/tls.go text: "G402:" - linters: - paralleltest # This test shouldn't run in parallel as it needs osext.Getwd. path: private/buf/buffetch/internal/reader_test.go text: missing the call to method parallel - linters: - paralleltest # Parallelizing TestPlainPostHandlerTLS and TestPlainPostHandlerH2C # makes this test flaky. path: private/buf/bufstudioagent/bufstudioagent_test.go text: missing the call to method parallel - linters: - gochecknoinits # we actually want to use init here path: private/bufpkg/bufconfig/module_config.go - linters: - gosec # We don't need a cryptographically secure RNG for these tests, and a # deterministic RNG is actually nice for test repeatability. path: private/bufpkg/bufimage/bufimageutil/.*_test\.go text: "G404:" - linters: - paralleltest # This test shouldn't run in parallel as it allocates a lot of memory. path: private/bufpkg/bufimage/build_image_unix_test.go text: missing the call to method parallel - linters: - containedctx # we actually want to embed a context here path: private/bufpkg/bufimage/parser_accessor_handler.go - linters: - containedctx # we actually want to embed a context here path: private/bufpkg/bufmodule/module.go - linters: - containedctx # we actually want to embed a context here path: private/bufpkg/bufmodule/module_set_builder.go - linters: - containedctx # we actually want to embed a context here path: private/pkg/standard/xos/xexec/process.go - linters: - gochecknoinits # we actually want to use init here path: private/bufpkg/bufmodule/paths.go - linters: - forbidigo # we want to use errgroup here path: private/bufpkg/bufremoteplugin/bufremoteplugindocker/docker.go text: use of `errgroup - linters: - forbidigo # this is one of two files we want to allow exec.Cmd functions in path: private/pkg/standard/xos/xexec/xexec.go - linters: - forbidigo # this is one of two files we want to allow exec.Cmd functions in path: private/pkg/standard/xos/xexec/process.go - linters: - gosec # G204 checks that exec.Command is not called with non-constants. path: private/pkg/standard/xos/xexec/xexec.go text: "G204:" - linters: - gosec # We should be able to use net/http/cgi in a unit test, in addition the CVE mentions # only versions of go < 1.6.3 are affected. path: private/pkg/git/git_test.go text: "G504:" - linters: - wastedassign # netrc/internal is a library largely copied in from an external repository with attribution. # We try to make minimal edits. path: private/pkg/netrc/internal/internal.go - linters: - forbidigo # We cache os.Getwd in osext, osext is the entrypoint. path: private/pkg/osext/osext.go text: os.Getwd - linters: - forbidigo # We cache os.Getwd in osext, osext has a Chdir that clears the cache. path: private/pkg/osext/osext.go text: os.Chdir - linters: - forbidigo # We're going to move xfilepath out. path: private/pkg/standard/xpath/xfilepath/xfilepath.go text: os.Getwd - linters: - gochecknoinits # protoencoding calls detrand.Disable via go:linkname and and init function. See the comments # in the file for more details. path: private/pkg/protoencoding/detrand.go - linters: - errcheck # headers.go has casts with values from contexts that should fail if there # is no error, but it would be very unidiomatic to return an error from # the functions that do these casts, and we completely control the # context values within this file path: private/pkg/rpc/headers.go - linters: - forbidigo # we use os.Rename here to rename files in the same directory # This is safe (we aren't traversing filesystem boundaries). path: private/pkg/storage/storageos/bucket.go text: os.Rename - linters: - containedctx # connCtx is cancelled when the connection is done; the context lifetime is # tied to the struct, not passed per-call. path: private/buf/buflsp/buflsp.go - linters: - containedctx # we actually want to embed a context here path: private/pkg/transport/grpc/grpcclient/client_conn_provider.go - linters: - forbidigo # we actually want to use errgroup when starting an HTTP server path: private/pkg/transport/http/httpserver/httpserver.go - linters: - staticcheck text: "ST1005:" - linters: - gochecknoinits # we actually want to use this init path: private/usage/usage.go - linters: - staticcheck # We deprecated all the definitions in plugin.proto but we still implement them. text: buf/alpha/registry/v1alpha1/plugin.proto is marked as deprecated - linters: - staticcheck # We deprecated all the definitions in generate.proto but we still implement them. text: buf/alpha/registry/v1alpha1/generate.proto is marked as deprecated - linters: - staticcheck # This greatly simplifies creation of descriptors, and it's safe enough since # it's just test code. text: GetDeprecatedLegacyJsonFieldConflicts is deprecated - linters: - forcetypeassert path: private/bufpkg/bufimage/source_retention_options_test\.go - linters: - paralleltest # The LsModules tests call chdir and cannot be parallelized. path: cmd/buf/buf_test.go text: LsModules - linters: - gosec # G101 checks for hardcoded credentials, and the variables named "*Token* # trip this off. path: private/pkg/oauth2/device.go text: "G101:" # G115 checks for integer overflow from integer conversions. There are known false # positives from the check (https://github.com/securego/gosec/issues/1212) that are # actively being worked on. Each exemption below is a false positive or for a safe operation, # such as parsing indices from descriptors and/or images. - linters: - gosec # Loop index conversion to uint64. path: private/buf/bufgen/features.go text: "G115:" - linters: - gosec # Converting result from utf8.RuneCountInString to uint64. path: private/bufpkg/bufcheck/bufcheckserver/internal/buflintvalidate/field.go text: "G115:" - linters: - gosec # PluginReference revision is validated with a bounds check at construction time. path: private/bufpkg/bufremoteplugin/bufremoteplugin.go text: "G115:" - linters: - gosec # A bounds check has been added for int32 -> uint32 conversion this is being flagged # as a false positive. path: private/buf/bufcurl/reflection_resolver.go text: "G115:" - linters: - gosec # bufprotosource converts indices to int32 to form the source path. Since it is parsing # from the fileDescriptor set, the operation should be safe. path: private/bufpkg/bufprotosource/paths.go text: "G115:" - linters: - gosec # Bounds checks have been added with assertion statements to ensure safe int -> int32 # conversions, this is a false positive. path: private/bufpkg/bufprotosource/option_extension_descriptor_test.go text: "G115:" - linters: - gosec # This converts results from strconv.ParseInt with the bit size set to 32 to int32, # so it should be a safe conversion, this is a false positive. path: private/buf/bufprotopluginexec/version.go text: "G115:" - linters: - gosec # This checks the cel constraints from an Image, and converts loop indices to int32 # to set the source path for the location, this operation should be safe. path: private/bufpkg/bufcheck/bufcheckserver/internal/buflintvalidate/cel.go text: "G115:" # No obvious deprecated replacement. - linters: - staticcheck path: private/pkg/protoencoding/reparse_extensions_test.go text: "SA1019:" - linters: - staticcheck # We still need to strip the "weak" option from fields until it is fully deprecated path: private/pkg/protoencoding/strip_legacy_options.go # Allow marshal and unmarshal functions in protoencoding only - linters: - forbidigo path: private/pkg/protoencoding text: proto.Marshal - linters: - forbidigo path: private/pkg/protoencoding text: proto.Unmarshal - linters: - forbidigo path: private/pkg/protoencoding text: protojson.Marshal - linters: - forbidigo path: private/pkg/protoencoding text: protojson.Unmarshal - linters: - forbidigo path: private/pkg/protoencoding text: protoyaml.Marshal - linters: - forbidigo path: private/pkg/protoencoding text: protoyaml.Unmarshal - linters: - forbidigo path: private/pkg/protoencoding text: prototext.Marshal - linters: - forbidigo path: private/pkg/protoencoding text: prototext.Unmarshal - linters: - gosec # This checks the cel constraints for predefined rules from an Image, and converts loop indices to int32 # to set the source path for the location, this operation should be safe. path: private/bufpkg/bufcheck/bufcheckserver/internal/buflintvalidate/predefined_rules.go text: "G115:" - linters: - govet # We print command.Short to markdown, which is controlled and set by each command # and should be considered safe. path: private/bufpkg/bufcobra/markdown.go text: "printf: non-constant format string in call to p" - linters: - gosec # This converts slice indexes in a FileDescriptorProto to int32, # which are not an actual risk of overflow. path: private/bufpkg/bufimage/bufimageutil/image_filter.go text: "G115:" issues: max-same-issues: 0 formatters: enable: - gci - gofmt exclusions: generated: lax ================================================ FILE: .pre-commit-hooks.yaml ================================================ - id: buf-generate name: buf generate language: golang language_version: 1.25.6 entry: buf generate types: [proto] pass_filenames: false - id: buf-build name: buf build language: golang language_version: 1.25.6 entry: buf build types: [proto] pass_filenames: false - id: buf-breaking name: buf breaking language: golang language_version: 1.25.6 entry: buf breaking types: [proto] pass_filenames: false - id: buf-lint name: buf lint language: golang language_version: 1.25.6 entry: buf lint types: [proto] pass_filenames: false - id: buf-format name: buf format language: golang language_version: 1.25.6 entry: buf format -w --exit-code types: [proto] pass_filenames: false - id: buf-dep-update name: buf dep update language: golang language_version: 1.25.6 entry: buf dep update files: '(buf\.lock|buf\.yaml)' pass_filenames: false - id: buf-dep-prune name: buf dep prune language: golang language_version: 1.25.6 entry: buf dep prune files: '(buf\.lock|buf\.yaml)' pass_filenames: false # Deprecated: use buf-dep-update instead. - id: buf-mod-update name: buf mod update language: golang language_version: 1.25.6 entry: buf mod update files: '(buf\.lock|buf\.yaml)' pass_filenames: false # Deprecated: use buf-dep-prune instead. - id: buf-mod-prune name: buf mod prune language: golang language_version: 1.25.6 entry: buf mod prune files: '(buf\.lock|buf\.yaml)' pass_filenames: false ================================================ FILE: CHANGELOG.md ================================================ # Changelog ## [Unreleased] - Add support for `--rbs_out` as a `protoc_builtin` plugin (requires protoc v34.0+). - Add relevant links from CEL LSP hover documentation to either or ## [v1.66.1] - 2026-03-09 - Fix `exclude_types` in `buf generate` dropping transitive dependencies of import files retained for their extension fields. ## [v1.66.0] - 2026-02-23 - Add LSP comment ignore code action to add comment ignores for lint errors. - Fix buf breaking module comparison when adding new modules. - Add LSP hover support for protovalidate CEL expressions. - Fixed offset handling in CEL semantic tokens for non-ASCII content and proto escape sequences in multi-line string literal expressions. - Improve URI normalization for the LSP. ## [v1.65.0] - 2026-02-03 - Add `buf registry policy {commit,create,delete,info,label,settings}` commands to manage BSR policies. - Add LSP deprecate code action to add the deprecated option on types and symbols. - Fix import LSP document link to not include `import` keyword. - Update `PROTOVALIDATE` lint rule to support checking unenforceable `required` rules on `repeated.items`, `map.keys` and `map.values`. - Add `buf source edit deprecate` command to deprecate types by setting the `deprecate = true` option. ## [v1.64.0] - 2026-01-19 - Fix LSP completion for options. - Add LSP document highlighting support. - Add LSP completion for fully-qualified type references. - Improve LSP semantic tokens implementation (for syntax highlighting). - Add LSP organize imports code action to add missing imports, remove unused imports, and sort imports alphabetically. - Add LSP document link support. - Add LSP folding range support. - Update `PROTOVALIDATE` lint rule to support checking `cel_expression` fields for valid CEL. ## [v1.63.0] - 2026-01-06 - Update `PROTOVALIDATE` lint rule to support field mask rules. - Add LSP completion for field numbers. ## [v1.62.1] - 2025-12-29 - Fix default behavior for `swift_prefix` to remain unset when no override is provided in managed mode. ## [v1.62.0] - 2025-12-29 - Add `swift_prefix` to managed mode. - Add `textDocument/rename` and `textDocument/prepareRename` support for `buf lsp serve`. - Fix panic in LSP for empty option paths. - Fix support for multi-arch image manifests for `buf beta registry plugin push`. ## [v1.61.0] - 2025-11-25 - Disable format on unknown or invalid syntax. - Fix regression in LSP functionality for well-known types. - Fix browser open for `buf registry login` in WSL2. - Fix panic in LSP for EOF lookups. - Fix `--create` flag for `buf push` to avoid errors on already existing modules if create is disallowed. ## [v1.60.0] - 2025-11-14 - Fix LSP published diagnostics to filter to the opened file. - Add `textDocument/documentSymbol` support for `buf lsp serve`. - Fix LSP navigation for cached modules which could cause import paths to become unresolvable. - Update default value of `--timeout` flag to 0, which results in no timeout by default. - Update `PROTOVALIDATE` lint rule to allow for custom rules that do not have `id` or `message` fields. ## [v1.59.0] - 2025-10-20 - Promote `buf beta lsp` to `buf lsp serve`. Command `buf beta lsp` is now deprecated. - Add `textDocument/References` support for `buf lsp serve`. - Add autocompletion for basic keywords, syntax, package and imports for `buf lsp serve`. - Add workspace symbol queries for `buf lsp serve`. - Fix positional encoding for diagnostics in `buf lsp serve`. - Fix format updates for `buf lsp serve`. - Fix syntax highlighting on semantic tokens for `buf lsp serve`. - Fix `buf format` to remove extraneous whitespace before the first header node (syntax/package declarations). ## [v1.58.0] - 2025-10-09 - Update `PROTOVALIDATE` lint rule to check `IGNORE_IF_ZERO_VALUE` on fields that track presence. - Fix `buf format` on fields with missing field number tags. - Optimize `include` and `exclude` path handling for workspaces to avoid unnecessary file system operations. This change can result in a performance improvement for large workspaces. - Fix buf curl for HTTP/2 services with ` --http2-prior-knowledge` flag set. ## [v1.57.2] - 2025-09-16 - Fix buf curl for HTTP/2 services. ## [v1.57.1] - 2025-09-16 - Minor bug fixes and dependency upgrades. ## [v1.57.0] - 2025-08-27 - Update exclude types to remove unused options reducing the size of generated code. - Add `gitlab-code-quality` error format to print errors in the GitLab Code Quality format for `buf lint` and `buf breaking`. - Add `source_control_url` to `json` outputs for `buf registry {module, plugin} commit` commands. ## [v1.56.0] - 2025-07-31 - Add `buf export --all` flag to include non-proto source files. - Add s390x binaries for Linux to releases. - Fix ppc64le binaries for Linux released as x86_64 binaries. - `buf lint` will no longer warn about uses of `(buf.validate.message).disabled`, as it was removed in protovalidate v0.14.0. Please update to protovalidate v0.14.0 or higher, using the steps outlined in the [protovalidate release notes](https://github.com/bufbuild/protovalidate/releases/tag/v0.14.0). - Fix `buf breaking --against-registry` to work with new modules that have no commits on the default branch. ## [v1.55.1] - 2025-06-17 - Fix language version for pre-commit hooks. ## [v1.55.0] - 2025-06-17 - Promote `buf beta stats` to `buf stats`. - Update built-in Well-Known Types to Protobuf v31.1. - Add `buf registry sdk info` command. - Allow workspaces that are adding new module(s) with no module-specific breaking configurations to run `buf breaking`, ignoring new module(s). ## [v1.54.0] - 2025-05-12 - Add `CSR` category to breaking rules. - Add support for local bufplugins for `protoc-gen-buf-breaking` and `protoc-gen-buf-lint`. - Add RISC-V (64-bit) binaries for Linux to releases. - Fix type filtering on `buf generate` for empty files, files with no declared types. - Fix CEL check on `buf lint` for predefined `rules` variables. - Fix `buf config migrate` to filter out removed rules. - Allow users to set examples without constraints in `PROTOVALIDATE` lint rule. - Add ppc64le binaries for Linux to releases. ## [v1.53.0] - 2025-04-21 - Fix buf breaking annotations for JSON format. ## [v1.52.1] - 2025-04-08 - Fix language version for pre-commit hooks. ## [v1.52.0] - 2025-04-07 - Fix `exclude_type` on a non imported package. - Fix `--exclude-type` flag for `buf generate` when an input is specified. - Fix type filter import filtering for options. - Add OS environment when invoking local buf plugins. - Add file path to `buf lint` and `buf breaking` output even when source code info is not available. This allows `buf lint` and `buf breaking` to respect `ignore` and `ignore_only` configurations when source code info is not available. ## [v1.51.0] - 2025-03-28 - Fix `buf convert` to allow for zero length for `binpb`, `txtpb`, and `yaml` formats. - Fix use of deprecated flag `--include-types` for `buf generate`. - Add `--against-registry` flag to `buf breaking` that runs breaking checks against the latest commit on the default branch of the corresponding module in the registry. - Fix type filter with unused image dependencies for `buf generate`. - Improve type filtering for `buf generate`. Adds the ability to exclude types with the parameter `exclude_types` in `buf.gen.yaml` and a flag `--exclude-types` in the CLI. Type filters may now also be specified as plugin parameters in `buf.gen.yaml`. ## [v1.50.1] - 2025-03-10 - Minor fixes and dependency updates. ## [v1.50.0] - 2025-01-17 - Add input parameter `filter` for use with git inputs. This sets the filter flag argument for the git fetch command. ## [v1.49.0] - 2025-01-07 - Fix `buf plugin push --label` to allow pushing a plugin with a label. - Add `--digest-changes-only` flag to `buf registry {module,plugin} commit list` to filter out commits that have no digest changes. - Fix `buf plugin push --source-control-url` to allow pushing a plugin with the source control url. ## [v1.48.0] - 2024-12-19 - Add `buf registry plugin {create,delete,info,update}` commands to manage BSR plugins. - Breaking analysis support for `buf beta lsp`. - Fix bug when using the `--type` flag filter for `buf build` where import ordering is not deterministic. - Add `buf plugin push` command to push a plugin to the Buf Schema Registry. Only WebAssembly check plugins are supported at this time. - Add `buf plugin update` and `buf plugin prune` command to manage plugins in the `buf.lock` file. Only WebAssembly check plugins are supported at this time. - Add `buf registry plugin commit {add-label,info,list,resolve}` to manage BSR plugin commits. - Add `buf registry plugin label {archive,info,list,unarchive}` to manage BSR plugin commits. - Move `buf registry module update` to `buf registry module settings update`. Command `buf registry module update` is now deprecated. - Support remote check plugins in `buf lint` and `buf breaking` commands. ## [v1.47.2] - 2024-11-14 - Update the patch version to resolve NPM packaging issues. No command updates or user changes. ## [v1.47.1] - 2024-11-14 - Update the patch version to resolve NPM packaging issues. No command updates or user changes. ## [v1.47.0] - 2024-11-13 - Move `buf registry commit` to `buf registry module commit`. Command `buf registry commit` is now deprecated. - Move `buf registry label` to `buf registry module label`. Command `buf registry label` is now deprecated. ## [v1.46.0] - 2024-10-29 - Add `buf registry whoami` command, which checks if you are logged in to the Buf Schema Registry at a given domain. ## [v1.45.0] - 2024-10-08 - Update `buf registry module info --format=json` to add `default_label_name`, which provides the name of the default label of a module. ## [v1.44.0] - 2024-10-03 - Update the `PROTOVALIDATE` lint rule to check example field options. Examples will be checked that they satisfy the field constraints, and are only present if constraints are present. - Update the `PROTOVALIDATE` lint rule to check predefined rules. Predefined rules will be checked that they compile. - Add support for a WebAssembly (Wasm) runtime for custom lint and breaking changes plugins. Use the `.wasm` file extension to specify a path to a Wasm plugin. ## [v1.43.0] - 2024-09-30 - Add new experimental LSP support under `buf beta lsp`. ## [v1.42.0] - 2024-09-18 - Add support for custom lint and breaking change plugins. See [our launch blog post](https://buf.build/blog/buf-custom-lint-breaking-change-plugins) for more details! - Add `buf dep graph --format` flag that defaults to `dot`, and adds the option `json`, to print the dependency graph in JSON format. - Fix bugs in `buf format` where trailing comments on commas in message literals were not properly propagated to the formatted proto, empty message literals were not properly indented, and compound strings in options added an extra newline before trailing commas. ## [v1.41.0] - 2024-09-11 - Add HTTP/3 support for gRPC with `buf curl`. - Fix issue where errors from protoc plugins may be overwritten when executing plugins in parallel. ## [v1.40.1] - 2024-09-06 - Fix issue with `buf lint` where comment ignores in the shape of `// buf:lint:ignore ` were not recognized due to the extra comment. ## [v1.40.0] - 2024-09-04 - Add concept of a default lint or breaking rule, which is printed out as a property when running `buf config ls-{breaking,lint}-rules`. Default rules are those rules which are run if no lint or breaking rules are explicitly configured in your `buf.yaml`. - Rename `DEFAULT` lint rule category to `STANDARD`. With the concept of default rules being introduced, having a category named `DEFAULT` is confusing, as while it happens that all the rules in the `DEFAULT` lint category are also default rules, the name has become overloaded. As with all `buf` changes, this change is backwards-compatible: the `DEFAULT` lint category continues to work, and always will. We recommend changing to `STANDARD`, however. ## [v1.39.0] - 2024-08-27 - Fix git input handling of relative HEAD refs without branch names. - Add `includes` key to module configurations in v2 `buf.yaml`, accepting a list of directories. * If `includes` is specified, a proto file is considered in the module only if it is in one of the directories specified. * If both `includes` and `excludes` keys are specified for a module, a proto file is considered part of this module if it is contained in any of the include paths and not in any of the exclude paths. - Allow multiple module configurations in the same v2 `buf.yaml` to have the same directory path. ## [v1.38.0] - 2024-08-22 - Add `--http3` flag to `buf curl` which forces `buf curl` to use HTTP/3 as the transport. - Fix issue with directory inputs for v2 workspaces where the specified directory was not itself a path to a module, but contained directories with modules, and the modules would not build. - Stop creating empty `buf.lock` files when `buf dep update` does not find new dependencies to update and there is no existing `buf.lock`. - Update `buf push` to push the license file or doc file (e.g. `README.md`, `LICENSE`) in the same directory as `buf.yaml` if a module does not have a license file or doc file in the module's directory. - Fix constraints of `--path` flag for lint and breaking rules to avoid resolving all files within a module. This change can result in a performance improvement for large workspaces. ## [v1.37.0] - 2024-08-16 - Add `STABLE_PACKAGE_NO_IMPORT_UNSTABLE` lint rule which disallows files from stable packages to import files from unstable packages. - Fix plugin push failures when pushing an image built with containerd image store. ## [v1.36.0] - 2024-08-06 - Add `--list-services` and `--list-methods` flags to `buf curl`, which trigger the command to list known services or methods in the RPC schema, instead of invoking an RPC method. - Add `clean` as a top-level option in `buf.gen.yaml`, matching the `buf generate --clean` flag. If set to true, this will delete the directories, jar files, or zip files set to `out` for each plugin. - Fix git input handling of annotated tags. - Update `buf registry login` to complete the login flow in the browser by default. This allows users to login with their browser and have the token automatically provided to the CLI. - Add `buf registry organization {create, delete, info, update}` commands to manage BSR organizations. Remove `buf beta registry organization` commands. - Add `buf registry module {create, delete, deprecate, info, undeprecate, update}` commands to manage BSR modules. Remove `buf beta registry repository` commands. - Add `buf registry label {archive, info, list, unarchive}` commands to manage BSR module labels. Remove `buf beta registry label` commands and `buf beta registry {archive, unarchive}`. - Add `buf registry commit {add-label, info, list, resolve}` to manage BSR module commits. Remove `buf beta registry commit` commands. ## [v1.35.1] - 2024-07-24 - Fix the git input parameter `ref` to align with the `git` notion of a ref. This allows for the use of branch names, tag names, and commit hashes. - Fix unexpected `buf build` errors with absolute path directory inputs without workspace and/or module configurations (e.g. `buf.yaml`, `buf.work.yaml`) and proto file paths set to the `--path` flag. ## [v1.35.0] - 2024-07-22 - Add `buf generate --clean` flag that will delete the directories, jar files, or zip files that the plugins will write to, prior to generation. Allows cleaning of existing assets without having to call `rm -rf`. - Deprecate `--username` flag on and username prompt on `buf registry login`. A username is no longer required to log in. ## [v1.34.0] - 2024-06-21 - Add `buf config ls-modules` command to list configured modules. - Fix issue where `buf generate` would succeed on missing insertion points and panic on empty insertion point files. - Update `buf generate` to allow the use of Editions syntax when doing local code generation by proxying to a `protoc` binary (for languages where code gen is implemented inside of `protoc` instead of in a plugin: Java, C++, Python, etc). - Allow use of an array of strings for the `protoc_path` property of for `buf.gen.yaml`, where the first array element is the actual path and other array elements are extra arguments that are passed to `protoc` each time it is invoked. ## [v1.33.0] - 2024-06-13 - Allow user to override `--source-control-url` and `--create-default-label` when using `--git-metadata` with `buf push`. - Fix `buf push --git-metadata` when local tags point to different objects than the remote tags. - Fix issue where comment ignores were not respected for `PROTOVALIDATE` lint rule violations. - Add `buf beta registry label {create,get,list}` to replace `buf beta registry {draft, tag}` commands. - Update `buf beta commit {get,list}` command outputs to display create time and stop displaying associated tags. - Change the behavior of `buf beta commit list ` when the reference is empty. It now lists commits in the repository instead of listing commits of the default label. - Update output of `buf format` to canonicalize the punctuation used in message literals in option values. The output now always uses `{` and `}` instead of `<` and `>`; it adds `:` separators between field names and message values if the source omitted them, and it removes unnecessary separators between fields (`,` and `;` are allowed, but neither is needed). - Update `buf format -w` so that it does not touch files whose contents don't actually change. This eliminates noisy notifications to file-system-watcher tools that are watching the directory that contains proto sources. - Update `buf generate` to work with plugins provided by protoc for versions v24.0 to v25.3. Editions support was experimental in these releases, and the plugins advertise incomplete support for editions, which triggers `buf` to report an error. With this fix, these plugins can be used again as long as none of the input files use editions syntax. - Add `buf push --exclude-unnamed` flag to exclude unnamed modules when pushing to the BSR. ## [v1.32.2] - 2024-05-28 - Update `buf generate` to warn instead of error when proto3 optional is required but not supported by a plugin. ## [v1.32.1] - 2024-05-21 - Fix archive and git inputs so that `--path` and `--exclude-path` paths are relative to the `#subdir` rather than the root of the input. This fixes an unintended behavior change that was introduced in `v1.32.0`. - Add `module` input for `protoc-gen-buf-lint` and `protoc-gen-buf-breaking` to allow users to specify the module for `v2` configuration files. ## [v1.32.0] - 2024-05-16 - Add version `v2` for `buf.yaml` and `buf.gen.yaml` configuration files. - Add `buf config migrate` to migrate configuration files to the latest version (now `v2`). - Move `buf mod init` to `buf config init`. `buf mod init` is now deprecated. - Move `buf mod ls-lint-rules` to `buf config ls-lint-rules`. `buf mod ls-lint-rules` is now deprecated. - Move `buf mod ls-breaking-rules` to `buf config ls-breaking-rules`. `buf mod ls-breaking-rules` is now deprecated. - Move `buf mod prune` to `buf dep prune`. `buf mod prune` is now deprecated. - Move `buf mod update` to `buf dep update`. `buf mod update` is now deprecated. - Move `buf mod {clear-cache,cc}` to `buf registry cc`. `buf mod {clear-cache,cc}` is now deprecated. - Move `buf beta graph` to stable as `buf dep graph`. - Change the default visibility of `buf push --create-visibility` to `private` when the `--create` flag is set. Users are no longer required to set `--create-visibility` when running `buf push --create`. - Add `buf push --label`, which allows users to set labels when pushing new commits to the BSR. - Add `buf push --source-control-url`, which allows users to associate commits pushed to the BSR with a URL to a source code repository. - Add `buf push --create-default-label`, which allows users to set a default label for a repository when calling `buf push --create`. - Add `buf push --git-metadata`, which automatically sets appropriate `--label`, `--source-control-url`, and `--create-default-label` flags based on the current Git repository. - Add `buf convert --validate` to apply [protovalidate](https://github.com/bufbuild/protovalidate) rules to incoming messages specified with `--from`. - Deprecate `buf mod open`. - Delete `buf beta migrate-v1beta1` This is now replaced with `buf config migrate`. - Add `buf registry sdk version` to get the version of a Generated SDK for a module and plugin. - Add `buf beta registry archive` and `buf beta registry unarchive` commands for archiving and unarchiving labels on the BSR. - Add support for Protobuf Editions. This allows `buf` to be used with sources that use edition 2023, instead of proto2 or proto3 syntax. This also updates the `protoc-gen-buf-breaking` and `protoc-gen-buf-lint` Protobuf plugins to support files that use edition 2023. - Update `buf breaking` rules to work with Protobuf Editions. To support Editions, some rules have been deprecated and replaced with Editions-aware rules. All deprecated rules continue to work for existing users. * `FIELD_SAME_CTYPE` has been replaced with `FIELD_SAME_CPP_STRING_TYPE`, which considers both `ctype` field options and new `(pb.cpp).string_type` features when deciding on backwards compatibility. * `FIELD_SAME_LABEL` has been replaced with three rules that all check "cardinality". The new rules can distinguish between maps and other repeated fields and between implicit and explicit field presence. The new rules are: 1. `FIELD_SAME_CARDINALITY` in the `FILE` and `PACKAGE` categories. 2. `FIELD_WIRE_COMPATIBLE_CARDINALITY` in the `WIRE` category. 3. `FIELD_WIRE_JSON_COMPATIBLE_CARDINALITY` in the `WIRE_JSON` category. * `FILE_SAME_JAVA_STRING_CHECK_UTF8` has been replaced with `FIELD_SAME_JAVA_UTF8_VALIDATION`, which considers both the `java_string_check_utf8` file option and `(pb.java).utf8_validation` features when deciding on backwards compatibility. * Add to the existing `FILE_SAME_SYNTAX` rule with a few related rules that can catch the same sort of compatibility issues, but in an Editions source file that changes feature values: 1. `MESSAGE_SAME_JSON_FORMAT` and `ENUM_SAME_JSON_FORMAT` catch changes to the `json_format` feature, which controls whether support for the JSON format is best-effort or properly supported. When supported, the compiler performs more checks relating to field name collisions for the JSON format as well as for FieldMask usage. 2. `FIELD_SAME_UTF8_VALIDATION` catches changes to the `utf8_validation` feature, which controls validation of string values. 3. `ENUM_SAME_TYPE` catches changes to an enum's type, open vs. closed. - Add support for extensions to `buf breaking`. All existing rules for fields are now applied to extensions, except for `FIELD_NO_DELETE` (and its variants). There are also new `EXTENSION_NO_DELETE` and `PACKAGE_EXTENSION_NO_DELETE` rules for catching deletions of an extension. The new rules are not active by default in existing `v1` and `v1beta1` configurations, for backwards-compatibility reasons. Migrate your config to `v2` to use them. - Add support for top-level extensions to `buf lint`. It previously only checked extensions that were defined inside of messages. - Add a new `FIELD_NOT_REQUIRED` lint rule that prevents use of required in proto2 files and of `features.field_presence = LEGACY_REQUIRED` in Editions files. This new rule is not active by default in existing `v1` and `v1beta1` configurations, for backwards-compatibility reasons. Migrate your config to `v2` to use them. ## [v1.32.0-beta.1] - 2024-04-23 - Add `buf convert --validate` to apply [protovalidate](https://github.com/bufbuild/protovalidate) rules to incoming messages specified with `--from`. - Add `buf config migrate` to migrate configuration files to the latest version (now `v2`). - Promote `buf beta graph` to stable as `buf dep graph`. - Move `buf mod init` to `buf config init`. `buf mod init` is now deprecated. - Move `buf mod ls-lint-rules` to `buf config ls-lint-rules`. `buf mod ls-lint-rules` is now deprecated. - Move `buf mod ls-breaking-rules` to `buf config ls-breaking-rules`. `buf mod ls-breaking-rules` is now deprecated. - Move `buf mod prune` to `buf dep prune`. `buf mod prune` is now deprecated. - Move `buf mod update` to `buf dep update`. `buf mod update` is now deprecated. - Move `buf mod {clear-cache,cc}` to `buf registry cc`. `buf mod {clear-cache,cc}` is now deprecated. - Deprecate `buf mod open`. - Delete `buf beta migrate-v1beta1`. - Add `buf registry sdk version` to get the version of a Generated SDK for a module and plugin. ## [v1.31.0] - 2024-04-23 - Update dependencies. ## [v1.30.1] - 2024-04-03 - Fix issue where `buf lint` incorrectly reports an error for `(buf.validate.field).repeated` is set for a repeated validation rule. ## [v1.30.0] - 2024-03-07 - Update `buf generate` so it populates the recently-added [`source_file_descriptors`](https://github.com/protocolbuffers/protobuf/blob/v24.0/src/google/protobuf/compiler/plugin.proto#L96-L99) field of the `CodeGeneratorRequest` message. This provides the plugin with access to options that are configured to only be retained in source and not at runtime (via [field option](https://github.com/protocolbuffers/protobuf/blob/v24.0/src/google/protobuf/descriptor.proto#L693-L702)). Descriptors in the `proto_file` field will not include any options configured this way for the files named in `file_to_generate` field. - Add `--exclude-source-retention-options` flag to `buf build`, which causes options configured to only be retained in source to be stripped from the output descriptors. ## [v1.29.0] - 2024-01-24 - Add support for `yaml` format. All commands that take image inputs, output images, or convert between message formats, now take `yaml` as a format, in addition to the existing `binpb` and `txtpb` formats. Some examples: - `buf build -o image.yaml` - `buf ls-files image.yaml` - `buf convert --type foo.Bar --from input.binpb --to output.yaml` - The `yaml` and `json` formats now accept two new options: `use_proto_names` and `use_enum_numbers`. This affects output serialization. Some examples: - `buf convert --type foo.Bar --from input.binpb --to output.yaml#use_proto_names=true` - `buf convert --type foo.Bar --from input.binpb --to -#format=yaml,use_enum_numbers=true` - Fix issue where `buf format` would inadvertently mangle files that used the [expanded `Any` syntax](https://protobuf.com/docs/language-spec#any-messages) in option values. ## [v1.28.1] - 2023-11-15 - The `buf curl` command has been updated to support the use of multiple schemas. This allows users to specify multiple `--schema` flags and/or to use both `--schema` and `--reflect` flags at the same time. The result is that additional sources can be consulted to resolve an element. This can be useful when the result of an RPC contains extensions or values in `google.protobuf.Any` messages that are not defined in the same schema that defines the RPC service. - Fix issue where `buf lint` incorrectly reports error when `(buf.validate.field).required` is set for an optional field in proto3. ## [v1.28.0] - 2023-11-10 - Add lint rules for [protovalidate](https://github.com/bufbuild/protovalidate). `buf lint` will now verify that your protovalidate rules are valid. A single rule `PROTOVALIDATE` has been added to the `DEFAULT` group - given that protovalidate is net new, this does not represent a breaking change. - Update `buf beta price` with the latest pricing information. - Display a warning when reading a `buf.lock` with dependencies with b1 or b3 digests. b1 and b3 digests will be deprecated in a future version. Run `buf mod update` to update dependency digests. ## [v1.27.2] - 2023-10-27 - Fix issue where `buf build` and other commands may fail when handling certain archives created on macOS that contain files with extended attributes. ## [v1.27.1] - 2023-10-16 - Fix issue in v1.27.0 where `--path` did not work with workspaces under certain scenarios. ## [v1.27.0] - 2023-10-04 - Fix issue where `buf generate --exclude-path` was not properly excluding paths for remote modules. - Fix issue where `buf curl` had a user agent that did not properly place the extension as a suffix. - Update `buf beta price` with the latest pricing information. ## [v1.26.1] - 2023-08-09 - Fix issue where `buf build -o` did not properly output files with the `.txtpb` extension in Protobuf text format. ## [v1.26.0] - 2023-08-09 - Add support for the `--http2-prior-knowledge` flag when running `buf curl` against secure "https" URLs. This can be used with gRPC servers, that only support HTTP/2, when used with a network (layer 4) load balancer, that does not support protocol negotiation in TLS handshake. ## [v1.25.1] - 2023-08-02 - Fix issue where all files were being iterated over when using the `--path` flag. - Fix issue where the directory `.` was incorrectly accepted as a value for the `directories` key in `buf.work.yaml`. ## [v1.25.0] - 2023-07-18 - Add `txtpb` format to handle the Protobuf text format. and automatically recognize `.txtpb` files as Protobuf text files. The `txtpb` format can now be used with all `buf` commands that take images as input or output, such as `build`, `convert`, and `curl`. ## [v1.24.0] - 2023-07-13 - Update `buf mod update` to block updates that will result in conflicting `.proto` files across dependencies. - Replace `bin` format with `binpb` format, and support the `.binpb` file extension. `.binpb` is now the canonical file extension for binary-encoded Protobuf data. The `bin` format and the `.bin` file extension continue to be accepted. - Remove support for `go` subdomain in `.netrc`. This was used as part of the remote generation alpha, which has been fully deprecated in favor of remote plugins and remote packages. See https://buf.build/blog/remote-packages-remote-plugins-approaching-v1 for more details. - Update `buf beta price` with the latest pricing information. ## [v1.23.1] - 2023-06-30 - Fix issue where `buf beta graph` would not print modules within a workspace that had no dependencies or dependents. - Fix issue where `buf beta graph` would print warnings for missing dependencies that were actually present. ## [v1.23.0] - 2023-06-29 - Add `buf beta graph` to print the dependency graph for a module in DOT format. - Various small bug fixes. ## [v1.22.0] - 2023-06-23 - Change default for `--origin` flag of `buf beta studio-agent` to `https://buf.build` ## [v1.21.0] - 2023-06-05 - Fix issue where locally-produced images did not have module information if the corresponding module was stored in the new cache. - Remove `buf beta registry template`. - Remove `buf beta registry plugin {create,deprecate,list,undeprecate,version}` and replace with `buf beta registry plugin {push,delete}`. - Update `buf beta price` with the latest pricing information. ## [v1.20.0] - 2023-05-30 - Add `--emit-defaults` flag to `buf curl` to emit default values in JSON-encoded responses. - Indent JSON-encoded responses from `buf curl` by default. - Log a warning in case an import statement does not point to a file in the module, a file in a direct dependency, or a well-known type file. ## [v1.19.0] - 2023-05-17 - Add `--create` flag to `buf push` to create a repository if it does not exist. The user is also required to specify the visibility using `--create-visibility`. - Add `github-actions` error format to print errors in a form parseable by GitHub Actions. - Fix issue in `buf build` and `buf generate` where the use of type filtering (via `--type` flags) would cause the resulting image to have no source code info, even when `--exclude-source-info` was not specified. The main impact of the bug was that generated code would be missing comments. - Fix issue in `buf curl` when using `--user` or `--netrc` that would cause a malformed Authorization header to be sent. - Update the module cache to use an optimized content addressable store. The cache is now self-healing and uses significantly less space. Users wishing to free unused space can run `buf mod --clear-cache` once after upgrading to remove data stored in the previous module cache. ## [v1.18.0] - 2023-05-05 - Remove `buf beta registry {plugin,template} {deprecate,undeprecate}`. - Add `--user` and `--netrc` flags to `buf curl`, providing the same behavior as the flags of the same name in the cURL tool. - Include `DocumentationPath` in the module on `buf push`. - Support fallback paths, `README.md` and `README.markdown`, for module documentation. The default source for module documentation is `buf.md`. If `buf.md` is missing, `README.md` or `README.markdown` is used as fallback sources. ## [v1.17.0] - 2023-04-05 - Fix issue with JSON marshalling of errors where line and column fields were omitted when line and column information was empty. - Fix issue with MSVS marshalling of errors where the column could be 0. - Add `buf beta stats` command to print statistics about a given source or module. - Update `buf beta price` with the latest pricing information. ## [v1.16.0] - 2023-03-29 - Add `buf beta price` command to help users of the BSR figure out how much a module will cost to store on the BSR under the Teams or Pro plans. - Fix issue in `protoc-gen-buf-lint` that prevented it from reporting lint errors for unused imports. - Fix issue with `buf format` where indents would be produced on certain empty lines. - Remove `buf alpha registry token create` command. Tokens must be created through the BSR UI. - Add local WASM plugin support in alpha, gated by the `BUF_ALPHA_ENABLE_WASM` environment variable. This feature is under evaluation, and may change at any time. If you are interested in WASM Protobuf plugins, reach out to us. ## [v1.15.1] - 2023-03-08 - Fix bug in `buf generate` with `v1beta1` config files. - Fix potential crash when using the `--type` flag with `buf build` or `buf generate`. ## [v1.15.0] - 2023-02-28 - Update built-in Well-Known Types to Protobuf v22.0. - Fix bug in `buf format` where C-style block comments in which every line includes a prefix (usually "*") would be incorrectly indented. - Add `--private-network` flag to `buf beta studio-agent` to support handling CORS requests from Studio on private networks that set the `Access-Control-Request-Private-Network` header. ## [v1.14.0] - 2023-02-09 - Replace `buf generate --include-types` with `buf generate --type` for consistency. `--include-types` is now deprecated but continues to work, consistent with our compatibility guarantee. - Include type references in `google.protobuf.Any` messages in option values when filtering on type, e.g. with `buf build --type` or `buf generate --type`. - Allow specifying a specific `protoc` path in `buf.gen.yaml` when using `protoc`'s built-in plugins via the new `protoc_path` option. - Allow specifying arguments for local plugins in `buf.gen.yaml`. You can now do e.g. `path: ["go, "run", ./cmd/protoc-gen-foo]` in addition to `path: protoc-gen-foo`. - Add optional name parameter to `buf mod init`, e.g. `buf mod init buf.build/owner/foobar`. - Fix issue with `php_metadata_namespace` file option in [managed mode](https://docs.buf.build/generate/managed-mode). - Make all help documentation much clearer. If you notice any inconsistencies, let us know. ## [v1.13.1] - 2023-01-27 - Fix race condition with `buf generate` when remote plugins from multiple BSR instances are being used at once. ## [v1.13.0] - 2023-01-26 - Extend the `BUF_TOKEN` environment variable to accept tokens for multiple BSR instances. Both `TOKEN` and `TOKEN1@BSRHOSTNAME1,TOKEN2@BSRHOSTNAME2,...` are now valid values for `BUF_TOKEN`. - Remove `buf beta convert` in favor of the now-stable `buf convert`. ## [v1.12.0] - 2023-01-12 - Add `buf curl` command to invoke RPCs via [Connect](https://connect-build), [gRPC](https://grpc.io/), or [gRPC-Web](https://github.com/grpc/grpc-web.) - Introduce `objc_class_prefix` option in managed mode, allowing a `default` value for `objc_class_prefix` for all files, `except` and `override`, which both behave similarly to other `except` and `override` options. Specifying an empty `default` value is equivalent to having managed mode on in previous versions. - Introduce `ruby_package` option in managed mode, allowing `except` and `override`, in the same style as `objc_class_prefix`. Leaving `ruby_package` unspecified has the same effect as having mananged mode enabled in previous versions. ## [v1.11.0] - 2022-12-19 - `buf generate` now batches remote plugin generation calls for improved performance. - Update `optimize_for` option in managed mode, allowing a `default` value for `optimize_for` for all files, `except` and `override`, which both behave similarly to other `except` and `override` options. Specifying an `optimize_for` value in the earlier versions is equivalent to having a `optimize_for` with that value as default. ## [v1.10.0] - 2022-12-07 - When using managed mode, setting `enabled: false` now no longer fails `buf generate` and instead prints a warning log and ignores managed mode options. - Add `csharp_namespace` option to managed mode, allowing `except`, which excludes modules from managed mode, and `override`, which specifies `csharp_namespace` values per module, overriding the default value. By default, when managed mode is enabled, `csharp_namespace` is set to the package name with each package sub-name capitalized. - Promote `buf convert` to stable, keep `buf beta convert` aliased in the beta command. - Add `Types` filter to `buf generate` command to specify types (message, enum, service) that should be included in the image. When specified, the resulting image will only include descriptors to describe the requested types. ## [v1.9.0] - 2022-10-19 - New compiler that is faster and uses less memory than the outgoing one. - When generating source code info, the new compiler is 20% faster, and allocates 13% less memory. - If _not_ generating source code info, the new compiler is 50% faster and allocates 35% less memory. - In addition to allocating less memory through the course of a compilation, the new compiler releases some memory much earlier, allowing it to be garbage collected much sooner. This means that by the end of a very large compilation process, less than half as much memory is live/pinned to the heap, decreasing overall memory pressure. The new compiler also addresses a few bugs where Buf would accept proto sources that protoc would reject: - In proto3 files, field and enum names undergo a validation that they are sufficiently different so that there will be no conflicts in JSON names. - Fully-qualified names of elements (like a message, enum, or service) may not conflict with package names. - A oneof or extend block may not contain empty statements. - Package names may not be >= 512 characters in length or contain > 100 dots. - Nesting depth of messages may not be > 32. - Field types and method input/output types may not refer to synthetic map entry messages. - Push lint and breaking configuration to the registry. - Include `LICENSE` file in the module on `buf push`. - Formatter better edits/preserves whitespace around inline comments. - Formatter correctly indents multi-line block (C-style) comments. - Formatter now indents trailing comments at the end of an indented block body (including contents of message and array literals and elements in compact options) the same as the rest of the body (instead of out one level, like the closing punctuation). - Formatter uses a compact, single-line representation for array and message literals in option values that are sufficiently simple (single scalar element or field). - `buf beta convert` flags have changed from `--input` to `--from` and `--output`/`-o` to `--to` - fully qualified type names now must be parsed to the `input` argument and `--type` flag separately ## [v1.8.0] - 2022-09-14 - Change default for `--origin` flag of `buf beta studio-agent` to `https://studio.buf.build` - Change default for `--timeout` flag of `buf beta studio-agent` to `0` (no timeout). Before it was `2m` (the default for all the other `buf` commands). - Add support for experimental code generation with the `plugin:` key in `buf.gen.yaml`. - Preserve single quotes with `buf format`. - Support `junit` format errors with `--error-format`. ## [v1.7.0] - 2022-06-27 - Support protocol and encoding client options based on content-type in Studio Agent. - Add `--draft` flag to `buf push`. - Add `buf beta registry draft {list,delete}` commands. ## [v1.6.0] - 2022-06-21 - Fix issue where `// buf:lint:ignore` comment ignores did not work for the `ENUM_FIRST_VALUE_ZERO` rule. - Add `buf beta studio-agent` command to support the upcoming Buf Studio. ## [v1.5.0] - 2022-05-30 - Upgrade to `protoc` 3.20.1 support. - Fix an issue where `buf` would fail if two or more roots contained a file with the same name, but with different file types (i.e. a regular file vs. a directory). - Fix check for `PACKAGE_SERVICE_NO_DELETE` to detect deleted services. - Remove `buf beta registry track`. - Remove `buf beta registry branch`. ## [v1.4.0] - 2022-04-21 - Fix issue where duplicate synthetic oneofs (such as with proto3 maps or optional fields) did not result in a properly formed error. - Add `buf beta registry repository update` command which supports updating repository visibility (public vs private). As with all beta commands, this is likely to change in the future. ## [v1.3.1] - 2022-03-30 - Allow `--config` flag to be set when targeting a module within a workspace. - Update `buf format`'s file option order so that default file options are sorted before custom options. - Update `buf format` to write adjacent string literals across multiple lines. - Fix `buf format` so that the output directory (if any) is created if and only if the input is successfully formatted. ## [v1.3.0] - 2022-03-25 - Add `--exit-code` flag to `buf format` to exit with a non-zero exit code if the files were not already formatted. ## [v1.2.1] - 2022-03-24 - Fix a few formatting edge cases. ## [v1.2.0] - 2022-03-24 - Add `buf format` command to format `.proto` files. - Fix build scripts to avoid using the `command-line-arguments` pseudo-package when building binaries and re-introduce checking for proper usage of private packages. ## [v1.1.1] - 2022-03-21 - Remove check for proper usage of private packages due to a breaking change made in the Golang standard library in 1.18. ## [v1.1.0] - 2022-03-01 - Add `--type` flag to the `build` command to create filtered images containing only the specified types and their required dependencies. - Trim spaces and new lines from user-supplied token for `buf registry login`. - Add support for conversion between JSON and binary serialized message for `buf beta convert`. ## [v1.0.0] - 2022-02-17 - Check that the user provided a valid token when running `buf registry login`. - Add `buf mod open` that opens a module's homepage in a browser. - Add `buf completion` command to generate auto-completion scripts in commonly used shells. - Add `--disable-symlinks` flag to the `breaking, build, export, generate, lint, ls-files, push` commands. By default, the CLI will follow symlinks except on Windows, and this disables following symlinks. - Add `--include-wkt` flag to `buf generate`. When this flag is specified alongside `--include-imports`, this will result in the [Well-Known Types](https://github.com/bufbuild/wellknowntypes/tree/11ea259bf71c4d386131c1986ffe103cb1edb3d6/v3.19.4/google/protobuf) being generated as well. Most language runtimes have the Well-Known Types included as part of the core library, making generating the Well-Known Types separately undesirable. - Remove `buf protoc`. This was a pre-v1.0 demonstration to show that `buf` compilation produces equivalent results to mainline `protoc`, however `buf` is working on building a better Protobuf future that provides easier mechanics than our former `protoc`-based world. `buf protoc` itself added no benefit over mainline `protoc` beyond being considerably faster and allowing parallel compilation. If `protoc` is required, move back to mainline `protoc` until you can upgrade to `buf`. See [#915](https://github.com/bufbuild/buf/pull/915) for more details. - Context modifier no longer overrides an existing token on the context. This allows `buf registry login` to properly check the user provided token without the token being overridden by the CLI interceptor. - Removed the `buf config init` command in favor of `buf mod init`. - Removed the `buf config ls-breaking-rules` command in favor of `buf mod ls-breaking-rules`. - Removed the `buf config ls-lint-rules` command in favor of `buf mod ls-lint-rules`. - Removed the `buf config migrate-v1beta1` command in favor of `buf beta migrate-v1beta1`. - Add `buf beta decode` command to decode message with provided image source and message type. - Disable `--config` flag for workspaces. - Move default config version from `v1beta1` to `v1`. ## [v1.0.0-rc12] - 2022-02-01 - Add `default`, `except` and `override` to `java_package_prefix`. - Add dependency commits as a part of the `b3` digest. - Upgrade to `protoc` 3.19.4 support. - Remove `branch` field from `buf.lock`. ## [v1.0.0-rc11] - 2022-01-18 - Upgrade to `protoc` 3.19.3 support. - Add `PACKAGE_NO_IMPORT_CYCLE` lint rule to detect package import cycles. - Add `buf beta registry {plugin,template} {deprecate,undeprecate}`. - Add warning when using enterprise dependencies without specifying a enterprise remote in the module's identity. - Remove `digest`, and `created_at` fields from the `buf.lock`. This will temporarily create a new commit when pushing the same contents to an existing repository, since the `ModulePin` has been reduced down. - Add `--track` flag to `buf push` - Update `buf beta registry commit list` to allow a track to be specified. - Add `buf beta registry track {list,delete}` commands. - Add manpages for `buf`. ## [v1.0.0-rc10] - 2021-12-16 - Fix issue where remote references were not correctly cached. ## [v1.0.0-rc9] - 2021-12-15 - Always set `compiler_version` parameter in the `CodeGeneratorRequest` to "(unknown)". - Fix issue where `buf mod update` was unable to resolve dependencies from different remotes. - Display the user-provided Buf Schema Registry remote, if specified, instead of the default within the `buf login` message. - Fix issue where `buf generate` fails when the same plugin was specified more than once in a single invocation. - Update the digest algorithm so that it encodes the `name`, `lint`, and `breaking` configuration encoded in the `buf.yaml`. When this change is deployed, users will observe the following: - Users on `v0.43.0` or before will notice mismatched digest errors similar to the one described in https://github.com/bufbuild/buf/issues/661. - Users on `v0.44.0` or after will have their module cache invalidated, but it will repair itself automatically. - The `buf.lock` (across all versions) will reflect the new `b3-` digest values for new commits. ## [v1.0.0-rc8] - 2021-11-10 - Add new endpoints to the recommendation service to make it configurable. - Add `--exclude-path` flag to `buf breaking`, `buf build`, `buf export`, `buf generate`, and `buf lint` commands. This allows users to exclude specific paths when running commands. - Change `GetModulePackages` endpoint to return a repeated `ModulePackage` message that now includes package description with the package name. - Add `Oneof` to the `Message` structure for documentation. ## [v1.0.0-rc7] - 2021-11-08 - Upgrade to `protoc` 3.19.1 support. - Fix issue with `buf generate` where multiple insertion points are defined in the same file. ## [v1.0.0-rc6] - 2021-10-20 - Fix issue with `buf ls-files` when given an image as an input, imports were being printed, even without the `--include-imports` flag. - Add the ability for users to provide individual protobuf files as inputs to CLI commands. This allows users to run `buf` commands against and file input based on their current working directory, for example, `buf lint foo/bar.proto`, where `foo/bar.proto` is a path to protobuf file on disk. ## [v1.0.0-rc5] - 2021-10-12 - Add `buf beta registry repository deprecate` and `buf beta registry repository undeprecate`. - Support `--include-imports` for remote plugins. - Fix issue where `buf config migrate-v1beta1 fails` when files cannot be renamed. - Fix issue where `buf registry login` panics when an existing .netrc entry exists. ## [v1.0.0-rc4] - 2021-10-07 - Fix issue where `buf generate` could fail when used with large numbers of plugins and files on systems with low file limits. - Add `buf protoc --version` flag back. This was accidentally removed. - Upgrade to `protoc` 3.18.1 support. ## [v1.0.0-rc3] - 2021-10-04 - Add `--as-import-paths` flag to `ls-files` that strips local directory paths and prints file paths as they are imported. - Fix issue where groups used in custom options did not result in the same behavior as `protoc`. - Fix issue where insertion points were not applied with respect to the configured output directory. ## [v1.0.0-rc2] - 2021-09-23 - Add `--include-imports` flag to `ls-files`. - Upgrade to `protoc` 3.18.0 support. - Fix regression with git inputs using `recurse_submodules=true`. ## [v1.0.0-rc1] - 2021-09-15 This is our first v1.0 release candidate. This release largely concentrates on erroring for already-deprecated commands and flags. At Buf, we take compatibility very seriously. When we say v1.0, we mean it - we hope `buf` will be stable on v1 for the next decade, and if there is something we want to change, it is our responsibility to make sure that we don't break you, not your responsibility to change because of us. We have learned a lot about `buf` usage in the last two years of our beta, and have deprecated flags and commands as we go, but for v1.0, we are removing the deprecated items to make sure we have a clean setup going forward. All commands and flags have been printing warnings for a long time, and have an easy migration path. Simply update the command or flag, and you'll be good to go: - Removed the `buf login` command in favor of `buf registry login`. - Removed the `buf logout` command in favor of `buf registry logout`. - Removed the `buf mod init` command in favor of `buf config init`. - Removed the `--name` and `--dep` flags in `buf config init`. - Removed the `--log-level` global flag. - Moved the output of `--version` from stderr to stdout. - Moved the output of `--help` and `help` from stderr to stdout. - [From v0.55.0](https://github.com/bufbuild/buf/releases/tag/v0.55.0): The version key in all configuration files (`buf.yaml`, `buf.gen.yaml`, `buf.work.yaml`) is now required. - [From v0.45.0](https://github.com/bufbuild/buf/releases/tag/v0.45.0): Removed the `buf beta config init` command in favor of `buf config init`. - [From v0.45.0](https://github.com/bufbuild/buf/releases/tag/v0.45.0): Removed the `buf beta mod export` command in favor of `buf export`. - [From v0.45.0](https://github.com/bufbuild/buf/releases/tag/v0.45.0): Removed the `buf beta mod init` command in favor of `buf config init`. - [From v0.45.0](https://github.com/bufbuild/buf/releases/tag/v0.45.0): Removed the `buf beta mod update` command in favor of `buf mod update`. - [From v0.45.0](https://github.com/bufbuild/buf/releases/tag/v0.45.0): Removed the `buf beta mod clear-cache` command in favor of `buf mod clear-cache`. - [From v0.45.0](https://github.com/bufbuild/buf/releases/tag/v0.45.0): Removed the `buf beta push` command in favor of `buf push`. - [From v0.34.0](https://github.com/bufbuild/buf/releases/tag/v0.34.0): Removed the `buf check breaking` command in favor of `buf breaking`. - [From v0.34.0](https://github.com/bufbuild/buf/releases/tag/v0.34.0): Removed the `buf check lint` command in favor of `buf lint`. - [From v0.34.0](https://github.com/bufbuild/buf/releases/tag/v0.34.0): Removed the `buf check ls-lint-checkers` command in favor of `buf config ls-lint-rules`. - [From v0.34.0](https://github.com/bufbuild/buf/releases/tag/v0.34.0): Removed the `buf check ls-breaking-checkers` command in favor of `buf config ls-breaking-rules`. - [From v0.31.0](https://github.com/bufbuild/buf/releases/tag/v0.31.0): Removed the `--file` flag on `buf build` in favor of the `--path` flag. - [From v0.31.0](https://github.com/bufbuild/buf/releases/tag/v0.31.0): Removed the `--file` flag on `buf lint` in favor of the `--path` flag. - [From v0.31.0](https://github.com/bufbuild/buf/releases/tag/v0.31.0): Removed the `--file` flag on `buf breaking` in favor of the `--path` flag. - [From v0.31.0](https://github.com/bufbuild/buf/releases/tag/v0.31.0): Removed the `--file` flag on `buf generate` in favor of the `--path` flag. - [From v0.31.0](https://github.com/bufbuild/buf/releases/tag/v0.31.0): Removed the `--file` flag on `buf export` in favor of the `--path` flag. - [From v0.29.0](https://github.com/bufbuild/buf/releases/tag/v0.29.0): Removed the `--source` flag on `buf build` in favor of the first positional parameter. - [From v0.29.0](https://github.com/bufbuild/buf/releases/tag/v0.29.0): Removed the `--source-config` flag on `buf build` in favor of the `--config` flag. - [From v0.29.0](https://github.com/bufbuild/buf/releases/tag/v0.29.0): Removed the `--input` flag on `buf lint` in favor of the first positional parameter. - [From v0.29.0](https://github.com/bufbuild/buf/releases/tag/v0.29.0): Removed the `--input-config` flag on `buf lint` in favor of the `--config` flag. - [From v0.29.0](https://github.com/bufbuild/buf/releases/tag/v0.29.0): Removed the `--input` flag on `buf breaking` in favor of the first positional parameter. - [From v0.29.0](https://github.com/bufbuild/buf/releases/tag/v0.29.0): Removed the `--input-config` flag on `buf breaking` in favor of the `--config` flag. - [From v0.29.0](https://github.com/bufbuild/buf/releases/tag/v0.29.0): Removed the `--against-input` flag on `buf breaking` in favor of the `--against` flag. - [From v0.29.0](https://github.com/bufbuild/buf/releases/tag/v0.29.0): Removed the `--against-input-config` flag on `buf breaking` in favor of the `--against-config` flag. - [From v0.29.0](https://github.com/bufbuild/buf/releases/tag/v0.29.0): Removed the `--input` flag on `buf generate` in favor of the first positional parameter. - [From v0.29.0](https://github.com/bufbuild/buf/releases/tag/v0.29.0): Removed the `--input-config` flag on `buf generate` in favor of the `--config` flag. - [From v0.29.0](https://github.com/bufbuild/buf/releases/tag/v0.29.0): Removed the `--input` flag on `buf ls-files` in favor of the first positional parameter. - [From v0.29.0](https://github.com/bufbuild/buf/releases/tag/v0.29.0): Removed the `--input-config` flag on `buf ls-files` in favor of the `--config` flag. - [From v0.29.0](https://github.com/bufbuild/buf/releases/tag/v0.29.0): Removed the `buf image build` command in favor of `buf build`. - [From v0.29.0](https://github.com/bufbuild/buf/releases/tag/v0.29.0): Removed the `buf image convert` command. - [From v0.29.0](https://github.com/bufbuild/buf/releases/tag/v0.29.0): Removed the `buf beta image convert` command. - [From v0.23.0](https://github.com/bufbuild/buf/releases/tag/v0.23.0): Removed the `buf experimental image convert` command. - [From v0.52.0](https://github.com/bufbuild/buf/releases/tag/v0.52.0) [and v0.34.0](https://github.com/bufbuild/buf/releases/tag/v0.34.0): Complete deletion `protoc-gen-buf-check-breaking` and `protoc-gen-buf-check-lint`, which have been moved to `protoc-gen-buf-breaking` and `protoc-gen-buf-lint`. In January 2021 (v0.34.0), `protoc-gen-buf-check-breaking` and `protoc-gen-buf-check-lint` were deprecated and scheduled for removal for v1.0. In August 2021 (v0.52.0), we began returning error for every invocation of `protoc-gen-buf-check-breaking` and `protoc-gen-buf-check-lint`. This release completes the deletion process. The only migration necessary is to change your installation and invocation from `protoc-gen-buf-check-breaking` to `protoc-gen-buf-breaking` and `protoc-gen-buf-check-lint` to `protoc-gen-buf-lint`. These can be installed in the exact same manner, whether from GitHub Releases, Homebrew, AUR, or direct Go installation: ``` # instead of go get github.com/bufbuild/buf/cmd/protoc-gen-buf-check-breaking go get github.com/bufbuild/buf/cmd/protoc-gen-buf-breaking # instead of curl -sSL https://github.com/bufbuild/buf/releases/download/v0.57.0/protoc-gen-buf-check-breaking-Linux-x86_64 curl -sSL https://github.com/bufbuild/buf/releases/download/v0.57.0/protoc-gen-buf-breaking-Linux-x86_64 ``` ## [v0.56.0] - 2021-09-08 - Cascade `ENUM_ZERO_VALUE_SUFFIX` comment ignores from the enum level. - Fix issue where `buf generate --output` was not being respected in 0.55.0. ## [v0.55.0] - 2021-09-07 - Error if `version:` is not set in `buf.yaml`. This is one of the few breaking changes we must make before v1.0 to guarantee stability for the future. If you do not have a version set, simply add `version: v1beta1` to the top of your `buf.yaml`. - Support `BUF_TOKEN` for authentication. `buf` will now look for a token in the `BUF_TOKEN` environment variable, falling back to `.netrc` as set via `buf login`. - Add support for using remote plugins with local source files. - Add per-file overrides for managed mode. - Fix issue with the module cache where multiple simultaneous downloads would result in a temporarily-corrupted cache. - Hide verbose messaing behind the `--verbose` (`-v`) flag. - Add `--debug` flag to print out debug logging. ## [v0.54.1] - 2021-08-30 - Fix docker build. ## [v0.54.0] - 2021-08-30 - Add windows support. - Add `java_package_prefix` support to managed mode. - Fix issue with C# namespaces in managed mode. - Fix issue where `:main` was appended for errors containing references to modules. ## [v0.53.0] - 2021-08-25 - Fix issue where `buf generate --include-imports` would end up generating files for certain imports twice. - Error when both a `buf.mod` and `buf.yaml` are present. `buf.mod` was briefly used as the new default name for `buf.yaml`, but we've reverted back to `buf.yaml`. ## [v0.52.0] - 2021-08-19 Return error for all invocations of `protoc-gen-buf-check-breaking` and `protoc-gen-buf-check-lint`. As one of the few changes buf will ever make, `protoc-gen-buf-check-breaking` and `protoc-gen-buf-check-lint` were deprecated and scheduled for removal for v1.0 in January 2021. In preparation for v1.0, instead of just printing out a message notifying users of this, these commands now return an error for every invocation and will be completely removed when v1.0 is released. The only migration necessary is to change your installation and invocation from `protoc-gen-buf-check-breaking` to `protoc-gen-buf-breaking` and `protoc-gen-buf-check-lint` to `protoc-gen-buf-lint`. These can be installed in the exact same manner, whether from GitHub Releases, Homebrew, AUR, or direct Go installation: ``` # instead of go get github.com/bufbuild/buf/cmd/protoc-gen-buf-check-breaking go get github.com/bufbuild/buf/cmd/protoc-gen-buf-breaking # instead of curl -sSL https://github.com/bufbuild/buf/releases/download/v0.52.0/protoc-gen-buf-check-breaking-Linux-x86_64 curl -sSL https://github.com/bufbuild/buf/releases/download/v0.52.0/protoc-gen-buf-breaking-Linux-x86_64 ``` There is no change in functionality. ## [v0.51.1] - 2021-08-16 - Fix issue with git LFS where a remote must be set for fetch. ## [v0.51.0] - 2021-08-13 - Accept packages of the form `v\d+alpha` and `v\d+beta` as packages with valid versions. These will be considered unstable packages for the purposes of linting and breaking change detection if `ignore_unstable_packages` is set. - Fix issue with git clones that occurred when using a previous reference of the current branch. ## [v0.50.0] - 2021-08-12 - Add `buf generate --include-imports` that also generates all imports except for the Well-Known Types. - Fix issue where a deleted file within an unstable package that contained messages, enums, or services resulted in a breaking change failure if the `PACKAGE` category was used and `ignore_unstable_packages` was set. ## [v0.49.0] - 2021-08-10 - Split `FIELD_SAME_TYPE` breaking change rule into `FIELD_SAME_TYPE, FIELD_WIRE_COMPATIBLE_TYPE, FIELD_WIRE_JSON_COMPATIBLE_TYPE` in `v1`. See https://github.com/bufbuild/buf/pull/400 for details. - Only export imported dependencies from `buf export`. ## [v0.48.2] - 2021-07-30 - Fix git args for http auth with git lfs. ## [v0.48.1] - 2021-07-30 - Fix: use `-c` on `git` parent command instead of `--config` on `git fetch`. - Add `ruby_package` to managed mode. ## [v0.48.0] - 2021-07-29 - Add `buf export`. `buf export` will export the files from the specified input (default `"."`) to the given directory in a manner that is buildable by `protoc` without any `-I` flags. It also has options `--exclude-imports`, which excludes imports (and won't result in a buildable set of files), and `--path`, which filters to the specific paths. ## [v0.47.0] - 2021-07-29 - Rewrite the git cloner to use `git init && git fetch` rather than `git clone`. `git clone` is limited to local branches on the remote, whereas `git fetch` we can fetch any references on the remote including remote branches. - Add `php_namespace` managed mode handling. - Add `java_string_check_utf8` managed mode handling. ## [v0.46.0] - 2021-07-27 - Add `buf login` and `buf logout` to login and logout from the Buf Schema Registry. - Fix cache, configuration, and data environment variables for Windows. Note that while Windows is still not officially supported, `buf` largely works on Windows. ## [v0.45.0] - 2021-07-26 - Revert default configuration file location back from `buf.mod` to `buf.yaml`. Note that both continue to work. - Move default workspace configuration file location from `buf.work` to `buf.work.yaml`. Note that both continue to work. - Move `buf beta push` to `buf push`. Note that `buf beta push` continues to work. - Move most `buf beta mod` commands to `buf mod`. Note that all `buf beta mod` commands continue to work. - Add `--only` flag to `buf mod update`. - Warn if `buf.yaml` contains dependencies that are not represented in the `buf.lock` file. - Add `--version` flag to `buf config ls-{breaking,lint}-rules`. - Add `SYNTAX_SPECIFIED` lint rule to `BASIC, DEFAULT` categories for v1 configuration. - Add `IMPORT_USED` lint rule to `BASIC, DEFAULT` categories for v1 configuration. - Bring v1 configuration out of beta. - Add managed mode for `objc_class_prefix`, `csharp_namespace`. ## [v0.44.0] - 2021-07-08 - Fix issue where C++ scoping rules were not properly enforced. - Add support for splitting directory paths passed to `buf protoc -I` by a directory separator. - Fix Windows support for builtin `protoc` plugins when using `buf generate` or `buf protoc`. Note that Windows remains officially unsupported as we have not set up testing, but largely works. - Upgrade to `protoc` 3.17.3 support. - Change the default module configuration location from `buf.yaml` to `buf.mod`. Note that `buf.yaml` continues to work. - Continued work on the workspaces beta, including the `v1` configuration specification. - Continued work on the managed mode beta, including the `v1` configuration specification. - Add `v1` module configuration specification in beta - please continue to use `v1beta1` until the `v1` configuration specification is rolled out. - Add `buf config migrate-v1beta1`. ## [v0.43.2] - 2021-05-31 - Fix namespace resolution diff with protoc. ## [v0.43.1] - 2021-05-28 - Revert `protoc` namespace resolution diff change. ## [v0.43.0] - 2021-05-28 - Do not count `buf:lint:ignore` directives as valid comments for the `COMMENT_.*` lint rules. - Upgrade to `protoc` 3.17.1 support. - Fix namespace resolution diff with `protoc`. ## [v0.42.1] - 2021-05-20 - Change the architecture suffix of the Linux ARM release assets from `arm64` to `aarch64` to match the output of `uname -m` on Linux. ## [v0.42.0] - 2021-05-20 - Add managed mode in beta. This is a new feature that automatically sets file option values. - Add workspaces in beta. This is a new feature that allows multiple modules within the same directory structure. - Add arm64 releases. ## [v0.41.0] - 2021-04-01 * Add `MESSAGE_SAME_REQUIRED_FIELDS` breaking change rule. This checks to make sure no `required` fields are added or deleted from existing messages. * Support multi-architecture Docker image. * Exit with code 100 for `FileAnnotation` errors. ## [v0.40.0] - 2021-03-15 * Add `buf beta registry tag {create,list}` commands. * Add support for creating tags in `push` via `buf beta push -t`. * Fix an issue where errors were unnecessarily written in `buf lint` and `buf breaking`. ## [v0.39.1] - 2021-03-04 - Fix issue with CLI build process in 0.39.0. ## [v0.39.0] - 2021-03-04 * `buf beta push` doesn't create a new commit if the content of the push is the same as the latest commit on the branch. * Fix an issue where no error was shown when authentication failed. * Fix an issue where `buf protoc` would error if a plugin returned an empty error string. ## [v0.38.0] - 2021-02-25 - Update the tested `protoc` version for compatibility to 3.15.2. The `--experimental_allow_proto3_optional` flag is no longer set for versions >=3.15. - Update the Well-Known Types to 3.15.2. The `go_package` values for the Well-Known Types now point at google.golang.org/protobuf instead of github.com/golang/protobuf. ## [v0.37.1] - 2021-02-23 - Fix bug where authentication headers were not threaded through for certain Buf Schema Registry commands. - Fix issue where empty errors would incorrectly be wrapped by the CLI interceptor. - Update Buf module cache location to include remote. ## [v0.37.0] - 2021-02-09 - Add commands for the Buf Schema Registry. Visit our website to add yourself to [the waitlist](https://buf.build/waitlist). ## [v0.36.0] - 2021-01-18 Allows comment ignores of the form `// buf:lint:ignore ID` to be cascaded upwards for specific rules. - For `ENUM_VALUE_PREFIX, ENUM_VALUE_UPPER_SNAKE_CASE`, both the enum value and the enum are checked. - For `FIELD_LOWER_SNAKE_CASE, FIELD_NO_DESCRIPTOR`, both the field and message are checked. - For `ONEOF_LOWER_SNAKE_CASE`, both the oneof and message are checked. - For `RPC_NO_CLIENT_STREAMING, RPC_NO_SERVER_STREAMING, RPC_PASCAL_CASE, RPC_REQUEST_RESPONSE_UNIQUE`, both the method and service are checked. - For `RPC_REQUEST_STANDARD_NAME, RPC_RESPONSE_STANDARD_NAME`, the input/output type, method, and service are checked. ## [v0.35.1] - 2021-01-08 - Fix error when unmarshalling plugin configuration with no options (#236) ## [v0.35.0] - 2021-01-07 - Allow `opt` in `buf.gen.yaml` files to be either a single string, or a list of strings. Both of the following forms are accepted, and result in `foo=bar,baz,bat`: ```yaml version: v1beta1 plugins: - name: foo out: out opt: foo=bar,baz,bat ``` ```yaml version: v1beta1 plugins: - name: foo out: out opt: - foo=bar - baz - bat ``` ## [v0.34.0] - 2021-01-04 - Move `buf check lint` to `buf lint`. - Move `buf check breaking` to `buf breaking`. - Move `buf check ls-lint-checkers` to `buf config ls-lint-rules`. - Move `buf check ls-breaking-checkers` to `buf config ls-breaking-rules`. - Move `protoc-gen-buf-check-lint` to `protoc-gen-buf-lint`. - Move `protoc-gen-buf-check-breaking` to `protoc-gen-buf-breaking`. - Add `buf beta config init`. All previous commands continue to work in a backwards-compatible manner, and the previous `protoc-gen-buf-check-lint` and `protoc-gen-buf-check-breaking` binaries continue to be available at the same paths, however deprecation messages are printed. ## [v0.33.0] - 2020-12-12 - Add `strategy` option to `buf.gen.yaml` generation configuration. This allows selecting either plugin invocations with files on a per-directory basis, or plugin invocations with all files at once. See the [generation documentation](https://docs.buf.build/generate-usage) for more details. ## [v0.32.1] - 2020-12-10 - Fix issue where `SourceCodeInfo` for map fields within nested messages could be dropped. - Fix issue where deleted files would cause a panic when `breaking.ignore_unstable_packages = true`. ## [v0.32.0] - 2020-11-24 - Add symlink support for directory inputs. Symlinks will now be followed within your local directories when running `buf` commands. - Add the `breaking.ignore_unstable_packages` option to allow ignoring of unstable packages when running `buf check breaking`. See [the documentation](https://docs.buf.build/breaking-configuration#ignore_unstable_packages) for more details. - Enums that use the `allow_alias` option that add new aliases to a given number will no longer be considered breaking by `ENUM_VALUE_SAME_NAME`. See [the documentation](https://docs.buf.build/breaking-checkers#enum_value_same_name) for more details. ## [v0.31.1] - 2020-11-17 - Fix issue where `--experimental_allow_proto3_optional` was not set when proxying to `protoc` for the builtin plugins via `buf generate` or `buf protoc`. This flag is now set for `protoc` versions >= 3.12. ## [v0.31.0] - 2020-11-16 - Change the `--file` flag to `--path` and allow `--path` to take both files and directories, instead of just files with the old `--file`. This flag is used to filter the actual Protobuf files built under an input for most commands. You can now do for example `buf generate --path proto/foo` to only generate stubs for the files under `proto/foo`. Note that the `--file` flag continues to work, but prints a deprecation message. ## [v0.30.1] - 2020-11-12 - Relax validation of response file names from protoc plugins, so that when possible, plugins that are not compliant with the plugin specification are still usable with `buf generate`. ## [v0.30.0] - 2020-11-03 - Add `git://` protocol handling. ## [v0.29.0] - 2020-10-30 As we work towards v1.0, we are cleaning up the CLI UX. As part of this, we made the following changes: - `buf image build` has been moved to `buf build` and now accepts images as inputs. - `buf beta image convert` has been deleted, as `buf build` now covers this functionality. - The `-o` flag is no longer required for `buf build`, instead defaulting to the OS equivalent of `/dev/null`. - The `--source` flag on `buf build` has been deprecated in favor of passing the input as the first argument. - The `--source-config` flag on `buf build` has been moved to `--config`. - The `--input` flag on `buf check lint` has been deprecated in favor of passing the input as the first argument. - The `--input-config` flag on `buf check lint` has been moved to `--config`. - The `--input` flag on `buf check breaking` has been deprecated in favor of passing the input as the first argument. - The `--input-config` flag on `buf check breaking` has been moved to `--config`. - The `--against-input` flag on `buf check breaking` has been moved to `--against`. - The `--against-input-config` flag on `buf check breaking` has been moved to `--against-config`. - The `--input` flag on `buf generate` has been deprecated in favor of passing the input as the first argument. - The `--input-config` flag on `buf generate` has been moved to `--config`. - The `--input` flag on `buf ls-files` has been deprecated in favor of passing the input as the first argument. - The `--input-config` flag on `buf ls-files` has been moved to `--config`. We feel these changes make using `buf` more natural. Examples: ``` # compile the files in the current directory buf build # equivalent to the default no-arg invocation buf build . # build the repository at https://github.com/foo/bar.git buf build https://github.com/foo/bar.git # lint the files in the proto directory buf check lint proto # check the files in the current directory against the files on the master branch for breaking changes buf check breaking --against .git#branch=master # check the files in the proto directory against the files in the proto directory on the master branch buf check breaking proto --against .git#branch=master,subdir=proto ``` **Note that existing commands and flags continue to work.** While the deprecation messages will be printed, and we recommend migrating to the new invocations, your existing invocations have no change in functionality. ## [v0.28.0] - 2020-10-21 - Add `subdir` option for archive and git [Inputs](https://buf.build/docs/inputs). This allows placement of the `buf.yaml` configuration file in directories other than the base of your repository. You then can check against this subdirectory using, for example, `buf check breaking --against-input https://github.com/foo/bar.git#subdir=proto`. ## [v0.27.1] - 2020-10-16 - Fix minor typo in `buf help generate` documentation. ## [v0.27.0] - 2020-10-16 - Move `buf beta generate` out of beta to `buf generate`. This command now uses a template of configured plugins to generate stubs. See `buf help generate` for more details. ## [v0.26.0] - 2020-10-13 - Add jar and zip support to `buf protoc` and `buf beta generate`. ## [v0.25.0] - 2020-10-09 - Add the concept of configuration file version. The only currently-available version is `v1beta1`. See [buf.build/docs/faq](https://buf.build/docs/faq) for more details. ## [v0.24.0] - 2020-09-21 - Add fish completion to releases. - Update the `protoc` version for `buf protoc` to be `3.13.0`. ## [v0.23.0] - 2020-09-11 - Move the `experimental` parent command to `beta`. The command `buf experimental image convert` continues to work, but is deprecated in favor of `buf beta image convert`. - Add `buf beta generate`. ## [v0.22.0] - 2020-09-09 - Add [insertion point](https://github.com/protocolbuffers/protobuf/blob/cdf5022ada7159f0c82888bebee026cbbf4ac697/src/google/protobuf/compiler/plugin.proto#L135) support to `buf protoc`. ## [v0.21.0] - 2020-09-02 - Fix issue where `optional` fields in proto3 would cause the `ONEOF_LOWER_SNAKE_CASE` lint checker to fail. ## [v0.20.5] - 2020-07-24 - Fix issue where parser would fail on files starting with [byte order marks](https://en.wikipedia.org/wiki/Byte_order_mark#UTF-8). ## [v0.20.4] - 2020-07-21 - Fix issue where custom message options that had an unset map field could cause a parser failure. ## [v0.20.3] - 2020-07-18 - Fix issue where parameters passed with `--.*_opt` to `buf protoc` for builtin plugins were not properly propagated. ## [v0.20.2] - 2020-07-17 - Fix issue where roots containing non-proto files with the same path would cause an error. ## [v0.20.1] - 2020-07-14 - Fix issue where Zsh completion would fail due to some flags having brackets in their description. - Fix issue where non-builtin protoc plugin invocations would not have errors properly propagated. - Fix issue where multiple `--.*_opt` flags, `--.*_opt` flags with commas, or `--.*_out` flags with options that contained commas, would not be properly added. ## [v0.20.0] - 2020-07-13 - Add `--by-dir` flag to `buf protoc` that parallelizes generation per directory, resulting in a 25-75% reduction in the time taken to generate stubs for medium to large file sets. - Properly clean up temporary files and commands on interrupts. - Fix issue where certain files that started with invalid Protobuf would cause the parser to crash. ## [v0.19.1] - 2020-07-10 - Fix issue where stderr was not being propagated for protoc plugins in CLI mode. ## [v0.19.0] - 2020-07-10 - Add `protoc` command. This is a substitute for `protoc` that uses Buf's internal compiler. - Add `ENUM_FIRST_VALUE_ZERO` lint checker to the `OTHER` category. - Add support for the Visual Studio error format. ## [v0.18.1] - 2020-06-25 - Fix issue where linking errors for custom options that had a message type were not properly reported (#93) ## [v0.18.0] - 2020-06-22 - Handle custom options when marshalling JSON images (#87). - Add `buf experimental image convert` command to convert to/from binary/JSON images (#87). ## [v0.17.0] - 2020-06-17 - Add git ref support to allow specifying arbitrary git references as inputs (https://github.com/bufbuild/buf/issues/48). This allows you to do i.e. `buf check lint --input https://github.com/bufbuild/buf.git#ref=fa74aa9c4161304dfa83db4abc4a0effe886d253`. - Add `depth` input option when specifying git inputs with `ref`. This allows the user to configure the depth at which to clone the repository when looking for the `ref`. If specifying a `ref`, this defaults to 50. Otherwise, this defaults to 1. - Remove requirement for git branch or tag in inputs. This allows you to do i.e. `buf check lint --input https://github.com/bufbuild/buf.git` and it will automatically choose the default branch as an input. ## [v0.16.0] - 2020-06-02 - Add [proto3 optional](https://github.com/protocolbuffers/protobuf/blob/7cb5597013f0c4b978f02bce4330849f118aa853/docs/field_presence.md#how-to-enable-explicit-presence-in-proto3) support. ## [v0.15.0] - 2020-05-31 - Add opt-in comment-driven lint ignores via the `allow_comment_ignores` lint configuration option and `buf:lint:ignore ID` leading comment annotation (#73). ## [v0.14.0] - 2020-05-30 - Add `--file` flag to `buf image build` to only add specific files and their imports to outputted images. To exclude imports, use `--exclude-imports`. - Add `zip` as a source format. Buf can now read `zip` files, either locally or remotely, for image building, linting, and breaking change detection. - Add `zstd` as a compression format. Buf can now read and write Image files that are compressed using zstandard, and can read tarballs compressed with zstandard. - Deprecated: The formats `bingz, jsongz, targz` are now deprecated. Instead, use `format=bin,compression=gzip`, `format=json,compression=gzip`, or `format=tar,compression=gzip`. The formats `bingz, jsongz, targz` will continue to work forever and will not be broken, but will print a deprecation warning and we recommend updating. Automatic file extension parsing continues to work the same as well. ## [v0.13.0] - 2020-05-17 - Use the `git` binary instead of go-git for internal clones. This also enables using your system git credential management for git repositories cloned using https or ssh. See https://buf.build/docs/inputs#authentication for more details. ## [v0.12.1] - 2020-05-11 - Fix issue where roots were detected as overlapping if one root's name was a prefix of the other. ## [v0.12.0] - 2020-05-11 - Add netrc support for inputs. - Fix issue where filenames that contained `..` resulted in an error. - Internal: migrate to golang/protobuf v2. ## [v0.11.0] - 2020-04-09 - Add experimental flag `--experimental-git-clone` to use the `git` binary for git clones. ## [v0.10.0] - 2020-04-06 - Add `recurse_submodules` option for git inputs. Example: `https://github.com/foo/bar.git#branch=master,recurse_submodules=true` ## [v0.9.0] - 2020-03-25 - Fix issue where the option value ordering on an outputted `Image` was non-deterministic. - Fix issue where the `SourceCodeInfo` for the Well-Known Types was not included on an outputted `Image` when requested. ## [v0.8.0] - 2020-03-11 - Update dependencies. ## [v0.7.1] - 2020-03-05 - Tie HTTP download timeout to the `--timeout` flag. ## [v0.7.0] - 2020-01-31 - Add `tag` option for git inputs. ## [v0.6.0] - 2020-01-17 - Add `git` to the Docker container for local filesystem clones. - Update the JSON error format to use `path` as the file path key instead of `filename`. ## [v0.5.0] - 2020-01-01 - Allow basic authentication for remote tarballs, git repositories, and image files served from HTTPS endpoints. See https://buf.build/docs/inputs#https for more details. - Allow public key authentication for remote git repositories served from SSH endpoints. See https://buf.build/docs/inputs#ssh for more details. ## [v0.4.1] - 2019-12-30 - Fix issue where comparing enum values for enums that have `allow_alias` set and duplicate enum values present resulted in a system error. ## [v0.4.0] - 2019-12-05 - Change the breaking change detector to compare enum values on number instead of name. This also results in the `ENUM_VALUE_SAME_NUMBER` checker being replaced with the `ENUM_VALUE_SAME_NAME` checker, except this new checker is not in the `WIRE` category. ## [v0.3.0] - 2019-11-05 - Fix issue where multiple timeout errors were printed. - Add `buf check lint --error-format=config-ignore-yaml` to print out current lint errors in a format that can be copied into a configuration file. ## [v0.2.0] - 2019-10-28 - Add a Docker image for the `buf` binary. ## v0.1.0 - 2019-10-18 Initial beta release. [Unreleased]: https://github.com/bufbuild/buf/compare/v1.66.1...HEAD [v1.66.1]: https://github.com/bufbuild/buf/compare/v1.66.0...v1.66.1 [v1.66.0]: https://github.com/bufbuild/buf/compare/v1.65.0...v1.66.0 [v1.65.0]: https://github.com/bufbuild/buf/compare/v1.64.0...v1.65.0 [v1.64.0]: https://github.com/bufbuild/buf/compare/v1.63.0...v1.64.0 [v1.63.0]: https://github.com/bufbuild/buf/compare/v1.62.1...v1.63.0 [v1.62.1]: https://github.com/bufbuild/buf/compare/v1.62.0...v1.62.1 [v1.62.0]: https://github.com/bufbuild/buf/compare/v1.61.0...v1.62.0 [v1.61.0]: https://github.com/bufbuild/buf/compare/v1.60.0...v1.61.0 [v1.60.0]: https://github.com/bufbuild/buf/compare/v1.59.0...v1.60.0 [v1.59.0]: https://github.com/bufbuild/buf/compare/v1.58.0...v1.59.0 [v1.58.0]: https://github.com/bufbuild/buf/compare/v1.57.2...v1.58.0 [v1.57.2]: https://github.com/bufbuild/buf/compare/v1.57.1...v1.57.2 [v1.57.1]: https://github.com/bufbuild/buf/compare/v1.57.0...v1.57.1 [v1.57.0]: https://github.com/bufbuild/buf/compare/v1.56.0...v1.57.0 [v1.56.0]: https://github.com/bufbuild/buf/compare/v1.55.1...v1.56.0 [v1.55.1]: https://github.com/bufbuild/buf/compare/v1.55.0...v1.55.1 [v1.55.0]: https://github.com/bufbuild/buf/compare/v1.54.0...v1.55.0 [v1.54.0]: https://github.com/bufbuild/buf/compare/v1.53.0...v1.54.0 [v1.53.0]: https://github.com/bufbuild/buf/compare/v1.52.1...v1.53.0 [v1.52.1]: https://github.com/bufbuild/buf/compare/v1.52.0...v1.52.1 [v1.52.0]: https://github.com/bufbuild/buf/compare/v1.51.0...v1.52.0 [v1.51.0]: https://github.com/bufbuild/buf/compare/v1.50.1...v1.51.0 [v1.50.1]: https://github.com/bufbuild/buf/compare/v1.50.0...v1.50.1 [v1.50.0]: https://github.com/bufbuild/buf/compare/v1.49.0...v1.50.0 [v1.49.0]: https://github.com/bufbuild/buf/compare/v1.48.0...v1.49.0 [v1.48.0]: https://github.com/bufbuild/buf/compare/v1.47.2...v1.48.0 [v1.47.2]: https://github.com/bufbuild/buf/compare/v1.47.1...v1.47.2 [v1.47.1]: https://github.com/bufbuild/buf/compare/v1.47.0...v1.47.1 [v1.47.0]: https://github.com/bufbuild/buf/compare/v1.46.0...v1.47.0 [v1.46.0]: https://github.com/bufbuild/buf/compare/v1.45.0...v1.46.0 [v1.45.0]: https://github.com/bufbuild/buf/compare/v1.44.0...v1.45.0 [v1.44.0]: https://github.com/bufbuild/buf/compare/v1.43.0...v1.44.0 [v1.43.0]: https://github.com/bufbuild/buf/compare/v1.42.0...v1.43.0 [v1.42.0]: https://github.com/bufbuild/buf/compare/v1.41.0...v1.42.0 [v1.41.0]: https://github.com/bufbuild/buf/compare/v1.40.1...v1.41.0 [v1.40.1]: https://github.com/bufbuild/buf/compare/v1.40.0...v1.40.1 [v1.40.0]: https://github.com/bufbuild/buf/compare/v1.39.0...v1.40.0 [v1.39.0]: https://github.com/bufbuild/buf/compare/v1.38.0...v1.39.0 [v1.38.0]: https://github.com/bufbuild/buf/compare/v1.37.0...v1.38.0 [v1.37.0]: https://github.com/bufbuild/buf/compare/v1.36.0...v1.37.0 [v1.36.0]: https://github.com/bufbuild/buf/compare/v1.35.1...v1.36.0 [v1.35.1]: https://github.com/bufbuild/buf/compare/v1.35.0...v1.35.1 [v1.35.0]: https://github.com/bufbuild/buf/compare/v1.34.0...v1.35.0 [v1.34.0]: https://github.com/bufbuild/buf/compare/v1.33.0...v1.34.0 [v1.33.0]: https://github.com/bufbuild/buf/compare/v1.32.2...v1.33.0 [v1.32.2]: https://github.com/bufbuild/buf/compare/v1.32.1...v1.32.2 [v1.32.1]: https://github.com/bufbuild/buf/compare/v1.32.0...v1.32.1 [v1.32.0]: https://github.com/bufbuild/buf/compare/v1.32.0-beta.1...v1.32.0 [v1.32.0-beta.1]: https://github.com/bufbuild/buf/compare/v1.31.0...v1.32.0-beta.1 [v1.31.0]: https://github.com/bufbuild/buf/compare/v1.30.1...v1.31.0 [v1.30.1]: https://github.com/bufbuild/buf/compare/v1.30.0...v1.30.1 [v1.30.0]: https://github.com/bufbuild/buf/compare/v1.29.0...v1.30.0 [v1.29.0]: https://github.com/bufbuild/buf/compare/v1.28.1...v1.29.0 [v1.28.1]: https://github.com/bufbuild/buf/compare/v1.28.0...v1.28.1 [v1.28.0]: https://github.com/bufbuild/buf/compare/v1.27.2...v1.28.0 [v1.27.2]: https://github.com/bufbuild/buf/compare/v1.27.1...v1.27.2 [v1.27.1]: https://github.com/bufbuild/buf/compare/v1.27.0...v1.27.1 [v1.27.0]: https://github.com/bufbuild/buf/compare/v1.26.1...v1.27.0 [v1.26.1]: https://github.com/bufbuild/buf/compare/v1.26.0...v1.26.1 [v1.26.0]: https://github.com/bufbuild/buf/compare/v1.25.1...v1.26.0 [v1.25.1]: https://github.com/bufbuild/buf/compare/v1.25.0...v1.25.1 [v1.25.0]: https://github.com/bufbuild/buf/compare/v1.24.0...v1.25.0 [v1.24.0]: https://github.com/bufbuild/buf/compare/v1.23.1...v1.24.0 [v1.23.1]: https://github.com/bufbuild/buf/compare/v1.23.0...v1.23.1 [v1.23.0]: https://github.com/bufbuild/buf/compare/v1.22.0...v1.23.0 [v1.22.0]: https://github.com/bufbuild/buf/compare/v1.21.0...v1.22.0 [v1.21.0]: https://github.com/bufbuild/buf/compare/v1.20.0...v1.21.0 [v1.20.0]: https://github.com/bufbuild/buf/compare/v1.19.0...v1.20.0 [v1.19.0]: https://github.com/bufbuild/buf/compare/v1.18.0...v1.19.0 [v1.18.0]: https://github.com/bufbuild/buf/compare/v1.17.0...v1.18.0 [v1.17.0]: https://github.com/bufbuild/buf/compare/v1.16.0...v1.17.0 [v1.16.0]: https://github.com/bufbuild/buf/compare/v1.15.1...v1.16.0 [v1.15.1]: https://github.com/bufbuild/buf/compare/v1.15.0...v1.15.1 [v1.15.0]: https://github.com/bufbuild/buf/compare/v1.14.0...v1.15.0 [v1.14.0]: https://github.com/bufbuild/buf/compare/v1.13.1...v1.14.0 [v1.13.1]: https://github.com/bufbuild/buf/compare/v1.13.0...v1.13.1 [v1.13.0]: https://github.com/bufbuild/buf/compare/v1.12.0...v1.13.0 [v1.12.0]: https://github.com/bufbuild/buf/compare/v1.11.0...v1.12.0 [v1.11.0]: https://github.com/bufbuild/buf/compare/v1.10.0...v1.11.0 [v1.10.0]: https://github.com/bufbuild/buf/compare/v1.9.0...v1.10.0 [v1.9.0]: https://github.com/bufbuild/buf/compare/v1.8.0...v1.9.0 [v1.8.0]: https://github.com/bufbuild/buf/compare/v1.7.0...v1.8.0 [v1.7.0]: https://github.com/bufbuild/buf/compare/v1.6.0...v1.7.0 [v1.6.0]: https://github.com/bufbuild/buf/compare/v1.5.0...v1.6.0 [v1.5.0]: https://github.com/bufbuild/buf/compare/v1.4.0...v1.5.0 [v1.4.0]: https://github.com/bufbuild/buf/compare/v1.3.1...v1.4.0 [v1.3.1]: https://github.com/bufbuild/buf/compare/v1.3.0...v1.3.1 [v1.3.0]: https://github.com/bufbuild/buf/compare/v1.2.1...1.3.0 [v1.2.1]: https://github.com/bufbuild/buf/compare/v1.2.0...v1.2.1 [v1.2.0]: https://github.com/bufbuild/buf/compare/v1.1.1...v1.2.0 [v1.1.1]: https://github.com/bufbuild/buf/compare/v1.1.0...v1.1.1 [v1.1.0]: https://github.com/bufbuild/buf/compare/v1.0.0...v1.1.0 [v1.0.0]: https://github.com/bufbuild/buf/compare/v1.0.0-rc12...v1.0.0 [v1.0.0-rc12]: https://github.com/bufbuild/buf/compare/v1.0.0-rc11...v1.0.0-rc12 [v1.0.0-rc11]: https://github.com/bufbuild/buf/compare/v1.0.0-rc10...v1.0.0-rc11 [v1.0.0-rc10]: https://github.com/bufbuild/buf/compare/v1.0.0-rc9...v1.0.0-rc10 [v1.0.0-rc9]: https://github.com/bufbuild/buf/compare/v1.0.0-rc8...v1.0.0-rc9 [v1.0.0-rc8]: https://github.com/bufbuild/buf/compare/v1.0.0-rc7...v1.0.0-rc8 [v1.0.0-rc7]: https://github.com/bufbuild/buf/compare/v1.0.0-rc6...v1.0.0-rc7 [v1.0.0-rc6]: https://github.com/bufbuild/buf/compare/v1.0.0-rc5...v1.0.0-rc6 [v1.0.0-rc5]: https://github.com/bufbuild/buf/compare/v1.0.0-rc4...v1.0.0-rc5 [v1.0.0-rc4]: https://github.com/bufbuild/buf/compare/v1.0.0-rc3...v1.0.0-rc4 [v1.0.0-rc3]: https://github.com/bufbuild/buf/compare/v1.0.0-rc2...v1.0.0-rc3 [v1.0.0-rc2]: https://github.com/bufbuild/buf/compare/v1.0.0-rc1...v1.0.0-rc2 [v1.0.0-rc1]: https://github.com/bufbuild/buf/compare/v0.56.0...v1.0.0-rc1 [v0.56.0]: https://github.com/bufbuild/buf/compare/v0.55.0...v0.56.0 [v0.55.0]: https://github.com/bufbuild/buf/compare/v0.54.1...v0.55.0 [v0.54.1]: https://github.com/bufbuild/buf/compare/v0.54.0...v0.54.1 [v0.54.0]: https://github.com/bufbuild/buf/compare/v0.53.0...v0.54.0 [v0.53.0]: https://github.com/bufbuild/buf/compare/v0.52.0...v0.53.0 [v0.52.0]: https://github.com/bufbuild/buf/compare/v0.51.1...v0.52.0 [v0.51.1]: https://github.com/bufbuild/buf/compare/v0.51.0...v0.51.1 [v0.51.0]: https://github.com/bufbuild/buf/compare/v0.50.0...v0.51.0 [v0.50.0]: https://github.com/bufbuild/buf/compare/v0.49.0...v0.50.0 [v0.49.0]: https://github.com/bufbuild/buf/compare/v0.48.2...v0.49.0 [v0.48.2]: https://github.com/bufbuild/buf/compare/v0.48.1...v0.48.2 [v0.48.1]: https://github.com/bufbuild/buf/compare/v0.48.0...v0.48.1 [v0.48.0]: https://github.com/bufbuild/buf/compare/v0.47.0...v0.48.0 [v0.47.0]: https://github.com/bufbuild/buf/compare/v0.46.0...v0.47.0 [v0.46.0]: https://github.com/bufbuild/buf/compare/v0.45.0...v0.46.0 [v0.45.0]: https://github.com/bufbuild/buf/compare/v0.44.0...v0.45.0 [v0.44.0]: https://github.com/bufbuild/buf/compare/v0.43.2...v0.44.0 [v0.43.2]: https://github.com/bufbuild/buf/compare/v0.43.1...v0.43.2 [v0.43.1]: https://github.com/bufbuild/buf/compare/v0.43.0...v0.43.1 [v0.43.0]: https://github.com/bufbuild/buf/compare/v0.42.1...v0.43.0 [v0.42.1]: https://github.com/bufbuild/buf/compare/v0.42.0...v0.42.1 [v0.42.0]: https://github.com/bufbuild/buf/compare/v0.41.0...v0.42.0 [v0.41.0]: https://github.com/bufbuild/buf/compare/v0.40.0...v0.41.0 [v0.40.0]: https://github.com/bufbuild/buf/compare/v0.39.1...v0.40.0 [v0.39.1]: https://github.com/bufbuild/buf/compare/v0.39.0...v0.39.1 [v0.39.0]: https://github.com/bufbuild/buf/compare/v0.38.0...v0.39.0 [v0.38.0]: https://github.com/bufbuild/buf/compare/v0.37.1...v0.38.0 [v0.37.1]: https://github.com/bufbuild/buf/compare/v0.37.0...v0.37.1 [v0.37.0]: https://github.com/bufbuild/buf/compare/v0.36.0...v0.37.0 [v0.36.0]: https://github.com/bufbuild/buf/compare/v0.35.1...v0.36.0 [v0.35.1]: https://github.com/bufbuild/buf/compare/v0.35.0...v0.35.1 [v0.35.0]: https://github.com/bufbuild/buf/compare/v0.34.0...v0.35.0 [v0.34.0]: https://github.com/bufbuild/buf/compare/v0.33.0...v0.34.0 [v0.33.0]: https://github.com/bufbuild/buf/compare/v0.32.1...v0.33.0 [v0.32.1]: https://github.com/bufbuild/buf/compare/v0.32.0...v0.32.1 [v0.32.0]: https://github.com/bufbuild/buf/compare/v0.31.1...v0.32.0 [v0.31.1]: https://github.com/bufbuild/buf/compare/v0.31.0...v0.31.1 [v0.31.0]: https://github.com/bufbuild/buf/compare/v0.30.1...v0.31.0 [v0.30.1]: https://github.com/bufbuild/buf/compare/v0.30.0...v0.30.1 [v0.30.0]: https://github.com/bufbuild/buf/compare/v0.29.0...v0.30.0 [v0.29.0]: https://github.com/bufbuild/buf/compare/v0.28.0...v0.29.0 [v0.28.0]: https://github.com/bufbuild/buf/compare/v0.27.1...v0.28.0 [v0.27.1]: https://github.com/bufbuild/buf/compare/v0.27.0...v0.27.1 [v0.27.0]: https://github.com/bufbuild/buf/compare/v0.26.0...v0.27.0 [v0.26.0]: https://github.com/bufbuild/buf/compare/v0.25.0...v0.26.0 [v0.25.0]: https://github.com/bufbuild/buf/compare/v0.24.0...v0.25.0 [v0.24.0]: https://github.com/bufbuild/buf/compare/v0.23.0...v0.24.0 [v0.23.0]: https://github.com/bufbuild/buf/compare/v0.22.0...v0.23.0 [v0.22.0]: https://github.com/bufbuild/buf/compare/v0.21.0...v0.22.0 [v0.21.0]: https://github.com/bufbuild/buf/compare/v0.20.5...v0.21.0 [v0.20.5]: https://github.com/bufbuild/buf/compare/v0.20.4...v0.20.5 [v0.20.4]: https://github.com/bufbuild/buf/compare/v0.20.3...v0.20.4 [v0.20.3]: https://github.com/bufbuild/buf/compare/v0.20.2...v0.20.3 [v0.20.2]: https://github.com/bufbuild/buf/compare/v0.20.1...v0.20.2 [v0.20.1]: https://github.com/bufbuild/buf/compare/v0.20.0...v0.20.1 [v0.20.0]: https://github.com/bufbuild/buf/compare/v0.19.1...v0.20.0 [v0.19.1]: https://github.com/bufbuild/buf/compare/v0.19.0...v0.19.1 [v0.19.0]: https://github.com/bufbuild/buf/compare/v0.18.1...v0.19.0 [v0.18.1]: https://github.com/bufbuild/buf/compare/v0.18.0...v0.18.1 [v0.18.0]: https://github.com/bufbuild/buf/compare/v0.17.0...v0.18.0 [v0.17.0]: https://github.com/bufbuild/buf/compare/v0.16.0...v0.17.0 [v0.16.0]: https://github.com/bufbuild/buf/compare/v0.15.0...v0.16.0 [v0.15.0]: https://github.com/bufbuild/buf/compare/v0.14.0...v0.15.0 [v0.14.0]: https://github.com/bufbuild/buf/compare/v0.13.0...v0.14.0 [v0.13.0]: https://github.com/bufbuild/buf/compare/v0.12.1...v0.13.0 [v0.12.1]: https://github.com/bufbuild/buf/compare/v0.12.0...v0.12.1 [v0.12.0]: https://github.com/bufbuild/buf/compare/v0.11.0...v0.12.0 [v0.11.0]: https://github.com/bufbuild/buf/compare/v0.10.0...v0.11.0 [v0.10.0]: https://github.com/bufbuild/buf/compare/v0.9.0...v0.10.0 [v0.9.0]: https://github.com/bufbuild/buf/compare/v0.8.0...v0.9.0 [v0.8.0]: https://github.com/bufbuild/buf/compare/v0.7.1...v0.8.0 [v0.7.1]: https://github.com/bufbuild/buf/compare/v0.7.0...v0.7.1 [v0.7.0]: https://github.com/bufbuild/buf/compare/v0.6.0...v0.7.0 [v0.6.0]: https://github.com/bufbuild/buf/compare/v0.5.0...v0.6.0 [v0.5.0]: https://github.com/bufbuild/buf/compare/v0.4.1...v0.5.0 [v0.4.1]: https://github.com/bufbuild/buf/compare/v0.4.0...v0.4.1 [v0.4.0]: https://github.com/bufbuild/buf/compare/v0.3.0...v0.4.0 [v0.3.0]: https://github.com/bufbuild/buf/compare/v0.2.0...v0.3.0 [v0.2.0]: https://github.com/bufbuild/buf/compare/v0.1.0...v0.2.0 ================================================ FILE: Dockerfile.buf ================================================ FROM --platform=${BUILDPLATFORM} golang:1.26-alpine3.23 AS builder WORKDIR /workspace COPY go.mod go.sum /workspace/ RUN go mod download COPY cmd /workspace/cmd COPY private /workspace/private ARG TARGETOS ARG TARGETARCH RUN CGO_ENABLED=0 GOOS=${TARGETOS} GOARCH=${TARGETARCH} \ go build -ldflags "-s -w" -trimpath -buildvcs=false -o /go/bin/buf ./cmd/buf FROM alpine:3.23.3 RUN apk add --update --no-cache \ ca-certificates \ git \ openssh-client && \ rm -rf /var/cache/apk/* COPY --from=builder /go/bin/buf /usr/local/bin/buf ENTRYPOINT ["/usr/local/bin/buf"] ================================================ FILE: LICENSE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright 2020-2026 Buf Technologies, Inc. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: Makefile ================================================ MAKEGO := make/go MAKEGO_REMOTE := https://github.com/bufbuild/makego.git PROJECT := buf GO_MODULE := github.com/bufbuild/buf DOCKER_ORG := bufbuild DOCKER_PROJECT := buf include make/buf/all.mk ================================================ FILE: README.md ================================================ ![The Buf logo](./.github/buf-logo.svg) # Buf [![License](https://img.shields.io/github/license/bufbuild/buf?color=blue)](https://github.com/bufbuild/buf/blob/main/LICENSE) [![Release](https://img.shields.io/github/v/release/bufbuild/buf?include_prereleases)](https://github.com/bufbuild/buf/releases) [![CI](https://github.com/bufbuild/buf/workflows/ci/badge.svg)](https://github.com/bufbuild/buf/actions?workflow=ci) [![Docker](https://img.shields.io/docker/pulls/bufbuild/buf)](https://hub.docker.com/r/bufbuild/buf) [![Homebrew](https://img.shields.io/homebrew/v/buf)](https://github.com/bufbuild/homebrew-buf) [![Slack](https://img.shields.io/badge/slack-buf-%23e01563)][badges_slack] The [`buf`][buf] CLI is the best tool for working with [Protocol Buffers][protobuf]. It provides: - A [linter][lint_usage] that enforces good API design choices and structure. - A [breaking change detector][breaking_tutorial] that enforces compatibility at the source code or wire level. - A [generator][generate_usage] that invokes your plugins based on [configuration files][templates]. - A [formatter][format_usage] that formats your Protobuf files in accordance with industry standards. - Integration with the [Buf Schema Registry][bsr], including full dependency management. ## Installation ### Homebrew You can install `buf` using [Homebrew][brew] (macOS or Linux): ```sh brew install bufbuild/buf/buf ``` This installs: - The `buf`, [`protoc-gen-buf-breaking`][breaking], and [`protoc-gen-buf-lint`][lint] binaries - Shell completion scripts for [Bash], [Fish], [Powershell], and [zsh] ### Other methods For other installation methods, see our [official documentation][install], which covers: - Installing `buf` via [npm] - Installing `buf` on [Windows] - Using `buf` as a [Docker image][docker] - Installing as a [binary], from a [tarball], and from [source] through [GitHub Releases][releases] - [Verifying] releases using a [minisign] public key ## Usage Buf's help interface provides summaries for commands and flags: ```sh buf --help ``` For more comprehensive usage information, consult Buf's [documentation][docs], especially these guides: * [`buf breaking`][breaking_tutorial] * [`buf build`][build_usage] * [`buf generate`][generate_usage] * [`buf lint`][lint_usage] * [`buf format`][format_usage] * [`buf registry`][bsr_quickstart] (for using the [BSR]) ## CLI breaking change policy We will never make breaking changes within a given major version of the CLI. After `buf` reached v1.0, you can expect no breaking changes until v2.0. But as we have no plans to ever release a v2.0, we will likely never break the `buf` CLI. > This breaking change policy does _not_ apply to commands behind the `buf beta` gate, and you should expect breaking changes to commands like `buf beta registry`. The policy does go into effect, however, when those commands or flags are elevated out of beta. ## Our goals for Protobuf [Buf]'s goal is to replace the current paradigm of API development, centered around REST/JSON, with a **schema-driven** paradigm. Defining APIs using an [IDL] provides numerous benefits over REST/JSON, and [Protobuf] is by far the most stable and widely adopted IDL in the industry. We've chosen to build on this widely trusted foundation rather than creating a new IDL from scratch. But despite its technical merits, actually _using_ Protobuf has long been more challenging than it needs to be. The Buf CLI and the [BSR](#the-buf-schema-registry) are the cornerstones of our effort to change that for good and to make Protobuf reliable and easy to use for service owners and clients alike—in other words, to create a **modern Protobuf ecosystem**. While we intend to incrementally improve on the `buf` CLI and the [BSR](#the-buf-schema-registry), we're confident that the basic groundwork for such an ecosystem is _already_ in place. ## The Buf Schema Registry The [Buf Schema Registry][bsr] (BSR) is a SaaS platform for managing your Protobuf APIs. It provides a centralized registry and a single source of truth for all of your Protobuf assets, including not just your `.proto` files but also [remote plugins][bsr_plugins]. Although the BSR provides an intuitive browser UI, `buf` enables you to perform most BSR-related tasks from the command line, such as [pushing] Protobuf sources to the registry and managing [users] and [repositories]. > The BSR is not required to use `buf`. We've made the core [features] of the `buf` CLI available to _all_ Protobuf users. ## More advanced CLI features While `buf`'s [core features][features] should cover most use cases, we've included some more advanced features to cover edge cases: * **Automatic file discovery**. Buf walks your file tree and builds your `.proto` files in accordance with your supplied [build configuration][build_config], which means that you no longer need to manually specify `--proto_paths`. You can still, however, specify `.proto` files manually through CLI flags in cases where file discovery needs to be disabled. * **Fine-grained rule configuration** for [linting][lint_rules] and [breaking changes][breaking_rules]. While we do have recommended defaults, you can always select the exact set of rules that your use case requires, with [40 lint rules][lint_rules] and [53 breaking change rules][breaking_rules] available. * **Configurable error formats** for CLI output. `buf` outputs information in `file:line:column:message` form by default for each lint error and breaking change it encounters, but you can also select JSON, MSVS, JUnit, and Github Actions output. * **Editor integration** driven by `buf`'s granular error output. We currently provide linting integrations for both [Vim and Visual Studio Code][ide] and [JetBrains IDEs][jetbrains] like IntelliJ and GoLand, but we plan to support other editors such as Emacs in the future. * **Universal Input targeting**. Buf enables you to perform actions like linting and breaking change detection not just against local `.proto` files but also against a broad range of other [Inputs], such as tarballs and ZIP files, remote Git repositories, and pre-built [image][images] files. * **Speed**. Buf's internal Protobuf [compiler] compiles your Protobuf sources using all available cores without compromising deterministic output, which is considerably faster than `protoc`. This allows for near-instantaneous feedback, which is of special importance for features like [editor integration][ide]. ## Next steps Once you've installed `buf`, we recommend completing the [CLI tutorial][cli-tutorial], which provides a broad but hands-on overview of the core functionality of the CLI. The tour takes about 10 minutes to complete. After completing the tour, check out the remainder of the [docs] for your specific areas of interest. ## Builds The following is a breakdown of the binaries by CPU architecture and operating system available through our [releases]: | | Linux | MacOS | Windows | | --- | --- | --- | --- | | x86 (64-bit) | ✅ | ✅ | ✅ | | ARM (64-bit) | ✅ | ✅ | ✅ | | ARMv7 (32-bit) | ✅ | ❌ | ❌ | | RISC-V (64-bit) | ✅ | ❌ | ❌ | | ppc64le | ✅ | ❌ | ❌ | | s390x | ✅ | ❌ | ❌ | ## Community For help and discussion around Protobuf, best practices, and more, join us on [Slack][badges_slack]. For updates on the Buf CLI, [follow this repo on GitHub][repo]. For feature requests, bugs, or technical questions, email us at [dev@buf.build][email_dev]. For general inquiries or inclusion in our upcoming feature betas, email us at [info@buf.build][email_info]. [badges_slack]: https://buf.build/links/slack [bash]: https://www.gnu.org/software/bash [binary]: https://buf.build/docs/cli/installation/#source [breaking]: https://buf.build/docs/breaking/overview/ [breaking_rules]: https://buf.build/docs/breaking/rules/ [breaking_tutorial]: https://buf.build/docs/breaking/tutorial/ [brew]: https://brew.sh [bsr]: https://buf.build/docs/bsr/ [bsr_plugins]: https://buf.build/plugins [bsr_quickstart]: https://buf.build/docs/bsr/quickstart/ [buf]: https://buf.build [build_config]: https://buf.build/docs/build/usage/#key-concepts [build_usage]: https://buf.build/docs/build/usage [cli-tutorial]: https://buf.build/docs/cli/quickstart/ [compiler]: https://buf.build/docs/reference/internal-compiler/ [docker]: https://buf.build/docs/cli/installation/#docker [docs]: https://buf.build/docs [email_dev]: mailto:dev@buf.build [email_info]: mailto:info@buf.build [features]: #features [fish]: https://fishshell.com [format_usage]: https://buf.build/docs/format/style/ [generate_usage]: https://buf.build/docs/generate/tutorial/ [ide]: https://buf.build/docs/cli/editor-integration/ [idl]: https://en.wikipedia.org/wiki/Interface_description_language [images]: https://buf.build/docs/reference/images/ [inputs]: https://buf.build/docs/reference/inputs/ [install]: https://buf.build/docs/cli/installation/ [jetbrains]: https://buf.build/docs/cli/editor-integration/#jetbrains-ides [lint]: https://buf.build/docs/lint/overview/ [lint_rules]: https://buf.build/docs/lint/rules/ [lint_usage]: https://buf.build/docs/lint/tutorial/ [npm]: https://buf.build/docs/cli/installation/#npm [minisign]: https://github.com/jedisct1/minisign [powershell]: https://docs.microsoft.com/en-us/powershell [protobuf]: https://protobuf.dev [pushing]: https://buf.build/docs/bsr/module/publish/ [releases]: https://buf.build/docs/cli/installation/#github [repo]: https://github.com/bufbuild/buf/ [repositories]: https://buf.build/docs/concepts/repositories/ [source]: https://buf.build/docs/cli/installation/#source [tarball]: https://buf.build/docs/cli/installation/#github [templates]: https://buf.build/docs/configuration/v2/buf-gen-yaml/ [users]: https://buf.build/docs/admin/manage-members/ [verifying]: https://buf.build/docs/cli/installation/#github [windows]: https://buf.build/docs/cli/installation/#windows [zsh]: https://zsh.org ================================================ FILE: buf.yaml ================================================ version: v2 modules: - path: proto name: buf.build/bufbuild/buf lint: use: - STANDARD - UNARY_RPC disallow_comment_ignores: true breaking: use: - WIRE_JSON ignore_unstable_packages: true ================================================ FILE: cmd/buf/buf.go ================================================ // Copyright 2020-2026 Buf Technologies, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package main import ( "context" "crypto/tls" "errors" "fmt" "net" "net/http" "buf.build/go/app" "buf.build/go/app/appcmd" "buf.build/go/app/appext" "connectrpc.com/connect" "github.com/bufbuild/buf/cmd/buf/internal/command/alpha/protoc" "github.com/bufbuild/buf/cmd/buf/internal/command/alpha/registry/token/tokendelete" "github.com/bufbuild/buf/cmd/buf/internal/command/alpha/registry/token/tokenget" "github.com/bufbuild/buf/cmd/buf/internal/command/alpha/registry/token/tokenlist" "github.com/bufbuild/buf/cmd/buf/internal/command/beta/bufpluginv1" "github.com/bufbuild/buf/cmd/buf/internal/command/beta/bufpluginv1beta1" "github.com/bufbuild/buf/cmd/buf/internal/command/beta/bufpluginv2" "github.com/bufbuild/buf/cmd/buf/internal/command/beta/price" betaplugindelete "github.com/bufbuild/buf/cmd/buf/internal/command/beta/registry/plugin/plugindelete" betapluginpush "github.com/bufbuild/buf/cmd/buf/internal/command/beta/registry/plugin/pluginpush" "github.com/bufbuild/buf/cmd/buf/internal/command/beta/registry/webhook/webhookcreate" "github.com/bufbuild/buf/cmd/buf/internal/command/beta/registry/webhook/webhookdelete" "github.com/bufbuild/buf/cmd/buf/internal/command/beta/registry/webhook/webhooklist" "github.com/bufbuild/buf/cmd/buf/internal/command/beta/studioagent" "github.com/bufbuild/buf/cmd/buf/internal/command/breaking" "github.com/bufbuild/buf/cmd/buf/internal/command/build" "github.com/bufbuild/buf/cmd/buf/internal/command/config/configinit" "github.com/bufbuild/buf/cmd/buf/internal/command/config/configlsbreakingrules" "github.com/bufbuild/buf/cmd/buf/internal/command/config/configlslintrules" "github.com/bufbuild/buf/cmd/buf/internal/command/config/configlsmodules" "github.com/bufbuild/buf/cmd/buf/internal/command/config/configmigrate" "github.com/bufbuild/buf/cmd/buf/internal/command/convert" "github.com/bufbuild/buf/cmd/buf/internal/command/curl" "github.com/bufbuild/buf/cmd/buf/internal/command/dep/depgraph" "github.com/bufbuild/buf/cmd/buf/internal/command/dep/depprune" "github.com/bufbuild/buf/cmd/buf/internal/command/dep/depupdate" "github.com/bufbuild/buf/cmd/buf/internal/command/export" "github.com/bufbuild/buf/cmd/buf/internal/command/format" "github.com/bufbuild/buf/cmd/buf/internal/command/generate" "github.com/bufbuild/buf/cmd/buf/internal/command/lint" "github.com/bufbuild/buf/cmd/buf/internal/command/lsfiles" "github.com/bufbuild/buf/cmd/buf/internal/command/lsp/lspserve" "github.com/bufbuild/buf/cmd/buf/internal/command/mod/modlsbreakingrules" "github.com/bufbuild/buf/cmd/buf/internal/command/mod/modlslintrules" "github.com/bufbuild/buf/cmd/buf/internal/command/mod/modopen" "github.com/bufbuild/buf/cmd/buf/internal/command/plugin/pluginprune" "github.com/bufbuild/buf/cmd/buf/internal/command/plugin/pluginpush" "github.com/bufbuild/buf/cmd/buf/internal/command/plugin/pluginupdate" "github.com/bufbuild/buf/cmd/buf/internal/command/policy/policyprune" "github.com/bufbuild/buf/cmd/buf/internal/command/policy/policypush" "github.com/bufbuild/buf/cmd/buf/internal/command/policy/policyupdate" "github.com/bufbuild/buf/cmd/buf/internal/command/push" "github.com/bufbuild/buf/cmd/buf/internal/command/registry/module/modulecommit/modulecommitaddlabel" "github.com/bufbuild/buf/cmd/buf/internal/command/registry/module/modulecommit/modulecommitinfo" "github.com/bufbuild/buf/cmd/buf/internal/command/registry/module/modulecommit/modulecommitlist" "github.com/bufbuild/buf/cmd/buf/internal/command/registry/module/modulecommit/modulecommitresolve" "github.com/bufbuild/buf/cmd/buf/internal/command/registry/module/modulecreate" "github.com/bufbuild/buf/cmd/buf/internal/command/registry/module/moduledelete" "github.com/bufbuild/buf/cmd/buf/internal/command/registry/module/moduledeprecate" "github.com/bufbuild/buf/cmd/buf/internal/command/registry/module/moduleinfo" "github.com/bufbuild/buf/cmd/buf/internal/command/registry/module/modulelabel/modulelabelarchive" "github.com/bufbuild/buf/cmd/buf/internal/command/registry/module/modulelabel/modulelabelinfo" "github.com/bufbuild/buf/cmd/buf/internal/command/registry/module/modulelabel/modulelabellist" "github.com/bufbuild/buf/cmd/buf/internal/command/registry/module/modulelabel/modulelabelunarchive" "github.com/bufbuild/buf/cmd/buf/internal/command/registry/module/modulesettings/modulesettingsupdate" "github.com/bufbuild/buf/cmd/buf/internal/command/registry/module/moduleundeprecate" "github.com/bufbuild/buf/cmd/buf/internal/command/registry/organization/organizationcreate" "github.com/bufbuild/buf/cmd/buf/internal/command/registry/organization/organizationdelete" "github.com/bufbuild/buf/cmd/buf/internal/command/registry/organization/organizationinfo" "github.com/bufbuild/buf/cmd/buf/internal/command/registry/organization/organizationupdate" "github.com/bufbuild/buf/cmd/buf/internal/command/registry/plugin/plugincommit/plugincommitaddlabel" "github.com/bufbuild/buf/cmd/buf/internal/command/registry/plugin/plugincommit/plugincommitinfo" "github.com/bufbuild/buf/cmd/buf/internal/command/registry/plugin/plugincommit/plugincommitlist" "github.com/bufbuild/buf/cmd/buf/internal/command/registry/plugin/plugincommit/plugincommitresolve" "github.com/bufbuild/buf/cmd/buf/internal/command/registry/plugin/plugincreate" "github.com/bufbuild/buf/cmd/buf/internal/command/registry/plugin/plugindelete" "github.com/bufbuild/buf/cmd/buf/internal/command/registry/plugin/plugininfo" "github.com/bufbuild/buf/cmd/buf/internal/command/registry/plugin/pluginlabel/pluginlabelarchive" "github.com/bufbuild/buf/cmd/buf/internal/command/registry/plugin/pluginlabel/pluginlabelinfo" "github.com/bufbuild/buf/cmd/buf/internal/command/registry/plugin/pluginlabel/pluginlabellist" "github.com/bufbuild/buf/cmd/buf/internal/command/registry/plugin/pluginlabel/pluginlabelunarchive" "github.com/bufbuild/buf/cmd/buf/internal/command/registry/plugin/pluginsettings/pluginsettingsupdate" "github.com/bufbuild/buf/cmd/buf/internal/command/registry/policy/policycommit/policycommitaddlabel" "github.com/bufbuild/buf/cmd/buf/internal/command/registry/policy/policycommit/policycommitinfo" "github.com/bufbuild/buf/cmd/buf/internal/command/registry/policy/policycommit/policycommitlist" "github.com/bufbuild/buf/cmd/buf/internal/command/registry/policy/policycommit/policycommitresolve" "github.com/bufbuild/buf/cmd/buf/internal/command/registry/policy/policycreate" "github.com/bufbuild/buf/cmd/buf/internal/command/registry/policy/policydelete" "github.com/bufbuild/buf/cmd/buf/internal/command/registry/policy/policyinfo" "github.com/bufbuild/buf/cmd/buf/internal/command/registry/policy/policylabel/policylabelarchive" "github.com/bufbuild/buf/cmd/buf/internal/command/registry/policy/policylabel/policylabelinfo" "github.com/bufbuild/buf/cmd/buf/internal/command/registry/policy/policylabel/policylabellist" "github.com/bufbuild/buf/cmd/buf/internal/command/registry/policy/policylabel/policylabelunarchive" "github.com/bufbuild/buf/cmd/buf/internal/command/registry/policy/policysettings/policysettingsupdate" "github.com/bufbuild/buf/cmd/buf/internal/command/registry/registrycc" "github.com/bufbuild/buf/cmd/buf/internal/command/registry/registrylogin" "github.com/bufbuild/buf/cmd/buf/internal/command/registry/registrylogout" "github.com/bufbuild/buf/cmd/buf/internal/command/registry/sdk/sdkinfo" "github.com/bufbuild/buf/cmd/buf/internal/command/registry/sdk/version" "github.com/bufbuild/buf/cmd/buf/internal/command/registry/whoami" "github.com/bufbuild/buf/cmd/buf/internal/command/source/sourceedit/sourceeditdeprecate" "github.com/bufbuild/buf/cmd/buf/internal/command/stats" "github.com/bufbuild/buf/private/buf/bufcli" "github.com/bufbuild/buf/private/buf/bufctl" "github.com/bufbuild/buf/private/bufpkg/bufcobra" "github.com/bufbuild/buf/private/bufpkg/bufconnect" "github.com/bufbuild/buf/private/bufpkg/bufmodule" "github.com/bufbuild/buf/private/pkg/slogapp" "github.com/bufbuild/buf/private/pkg/syserror" "github.com/spf13/cobra" ) func main() { appcmd.Main(context.Background(), newRootCommand("buf")) } func newRootCommand(name string) *appcmd.Command { builder := appext.NewBuilder( name, appext.BuilderWithTimeout(0), appext.BuilderWithInterceptor(newErrorInterceptor()), appext.BuilderWithLoggerProvider(slogapp.LoggerProvider), ) return &appcmd.Command{ Use: name, Short: "The Buf CLI", Long: "A tool for working with Protocol Buffers and managing resources on the Buf Schema Registry (BSR)", Version: bufcli.Version, BindPersistentFlags: builder.BindRoot, SubCommands: []*appcmd.Command{ build.NewCommand("build", builder), export.NewCommand("export", builder), format.NewCommand("format", builder), lint.NewCommand("lint", builder), breaking.NewCommand("breaking", builder), generate.NewCommand("generate", builder), lsfiles.NewCommand("ls-files", builder), stats.NewCommand("stats", builder), push.NewCommand("push", builder), convert.NewCommand("convert", builder), curl.NewCommand("curl", builder), { Use: "dep", Short: "Work with dependencies", SubCommands: []*appcmd.Command{ depgraph.NewCommand("graph", builder), depprune.NewCommand("prune", builder, ``, false), depupdate.NewCommand("update", builder, ``, false), }, }, { Use: "config", Short: "Work with configuration files", SubCommands: []*appcmd.Command{ configinit.NewCommand("init", builder, ``, false, false), configmigrate.NewCommand("migrate", builder), configlslintrules.NewCommand("ls-lint-rules", builder), configlsbreakingrules.NewCommand("ls-breaking-rules", builder), configlsmodules.NewCommand("ls-modules", builder), }, }, { Use: "source", Short: "Work with Protobuf source files", SubCommands: []*appcmd.Command{ { Use: "edit", Short: "Edit Protobuf source files", SubCommands: []*appcmd.Command{ sourceeditdeprecate.NewCommand("deprecate", builder), }, }, }, }, { Use: "lsp", Short: "Work with Buf Language Server", SubCommands: []*appcmd.Command{ lspserve.NewCommand("serve", builder, ``, false, false), }, }, { Use: "mod", Short: `Manage Buf modules, all commands are deprecated and have moved to the "buf config", "buf dep", or "buf registry" subcommands.`, Deprecated: `all commands are deprecated and have moved to the "buf config", "buf dep", or "buf registry" subcommands.`, Hidden: true, SubCommands: []*appcmd.Command{ // Deprecated and hidden. configinit.NewCommand("init", builder, deprecatedMessage("buf config init", "buf mod init"), true, true), // Deprecated and hidden. depprune.NewCommand("prune", builder, deprecatedMessage("buf dep prune", "buf mod update"), true), // Deprecated and hidden. depupdate.NewCommand("update", builder, deprecatedMessage("buf dep update", "buf mod update"), true), // Deprecated and hidden. modopen.NewCommand("open", builder), // Deprecated and hidden. registrycc.NewCommand("clear-cache", builder, deprecatedMessage("buf registry cc", "buf mod clear-cache"), true, "cc"), // Deprecated and hidden. modlslintrules.NewCommand("ls-lint-rules", builder), // Deprecated and hidden. modlsbreakingrules.NewCommand("ls-breaking-rules", builder), }, }, { Use: "plugin", Short: "Work with check plugins", SubCommands: []*appcmd.Command{ pluginpush.NewCommand("push", builder), pluginupdate.NewCommand("update", builder), pluginprune.NewCommand("prune", builder), }, }, { Use: "policy", Short: "Work with policies", Hidden: true, SubCommands: []*appcmd.Command{ policypush.NewCommand("push", builder), policyupdate.NewCommand("update", builder), policyprune.NewCommand("prune", builder), }, }, { Use: "registry", Short: "Manage assets on the Buf Schema Registry", SubCommands: []*appcmd.Command{ registrylogin.NewCommand("login", builder), registrylogout.NewCommand("logout", builder), whoami.NewCommand("whoami", builder), registrycc.NewCommand("cc", builder, ``, false), { Use: "commit", Short: `Manage a module's commits, all commands are deprecated and have moved to the "buf registry module commit" subcommands`, Deprecated: `all commands are deprecated and have moved to the "buf registry module commit" subcommands.`, Hidden: true, SubCommands: []*appcmd.Command{ modulecommitaddlabel.NewCommand("add-label", builder, deprecatedMessage("buf registry module commit add-label", "buf registry commit add-label")), modulecommitinfo.NewCommand("info", builder, deprecatedMessage("buf registry module commit info", "buf registry commit info")), modulecommitlist.NewCommand("list", builder, deprecatedMessage("buf registry module commit list", "buf registry commit list")), modulecommitresolve.NewCommand("resolve", builder, deprecatedMessage("buf registry module commit resolve", "buf registry commit resolve")), }, }, { Use: "sdk", Short: "Manage Generated SDKs", SubCommands: []*appcmd.Command{ version.NewCommand("version", builder), sdkinfo.NewCommand("info", builder), }, }, { Use: "label", Short: `Manage a module's labels, all commands are deprecated and have moved to the "buf registry module label" subcommands`, Deprecated: `all commands are deprecated and have moved to the "buf registry module label" subcommands.`, Hidden: true, SubCommands: []*appcmd.Command{ modulelabelarchive.NewCommand("archive", builder, deprecatedMessage("buf registry module label archive", "buf registry label archive")), modulelabelinfo.NewCommand("info", builder, deprecatedMessage("buf registry module label info", "buf registry label info")), modulelabellist.NewCommand("list", builder, deprecatedMessage("buf registry module label list", "buf registry label list")), modulelabelunarchive.NewCommand("unarchive", builder, deprecatedMessage("buf registry module label unarchive", "buf registry label unarchive")), }, }, { Use: "organization", Short: "Manage organizations", SubCommands: []*appcmd.Command{ organizationcreate.NewCommand("create", builder), organizationdelete.NewCommand("delete", builder), organizationinfo.NewCommand("info", builder), organizationupdate.NewCommand("update", builder), }, }, { Use: "module", Short: "Manage BSR modules", SubCommands: []*appcmd.Command{ { Use: "commit", Short: "Manage a module's commits", SubCommands: []*appcmd.Command{ modulecommitaddlabel.NewCommand("add-label", builder, ""), modulecommitinfo.NewCommand("info", builder, ""), modulecommitlist.NewCommand("list", builder, ""), modulecommitresolve.NewCommand("resolve", builder, ""), }, }, { Use: "label", Short: "Manage a module's labels", SubCommands: []*appcmd.Command{ modulelabelarchive.NewCommand("archive", builder, ""), modulelabelinfo.NewCommand("info", builder, ""), modulelabellist.NewCommand("list", builder, ""), modulelabelunarchive.NewCommand("unarchive", builder, ""), }, }, { Use: "settings", Short: "Manage a module's settings", SubCommands: []*appcmd.Command{ modulesettingsupdate.NewCommand("update", builder, ""), }, }, modulecreate.NewCommand("create", builder), moduleinfo.NewCommand("info", builder), moduledelete.NewCommand("delete", builder), moduledeprecate.NewCommand("deprecate", builder), modulesettingsupdate.NewCommand("update", builder, deprecatedMessage("buf registry module settings update", "buf registry update")), moduleundeprecate.NewCommand("undeprecate", builder), }, }, { Use: "plugin", Short: "Manage BSR plugins", SubCommands: []*appcmd.Command{ { Use: "commit", Short: "Manage a plugin's commits", SubCommands: []*appcmd.Command{ plugincommitaddlabel.NewCommand("add-label", builder, ""), plugincommitinfo.NewCommand("info", builder, ""), plugincommitlist.NewCommand("list", builder, ""), plugincommitresolve.NewCommand("resolve", builder, ""), }, }, { Use: "label", Short: "Manage a plugin's labels", SubCommands: []*appcmd.Command{ pluginlabelarchive.NewCommand("archive", builder, ""), pluginlabelinfo.NewCommand("info", builder, ""), pluginlabellist.NewCommand("list", builder, ""), pluginlabelunarchive.NewCommand("unarchive", builder, ""), }, }, { Use: "settings", Short: "Manage a plugin's settings", SubCommands: []*appcmd.Command{ pluginsettingsupdate.NewCommand("update", builder), }, }, plugincreate.NewCommand("create", builder), plugininfo.NewCommand("info", builder), plugindelete.NewCommand("delete", builder), }, }, { Use: "policy", Short: "Manage BSR policies", SubCommands: []*appcmd.Command{ { Use: "commit", Short: "Manage a policy's commits", SubCommands: []*appcmd.Command{ policycommitaddlabel.NewCommand("add-label", builder, ""), policycommitinfo.NewCommand("info", builder, ""), policycommitlist.NewCommand("list", builder, ""), policycommitresolve.NewCommand("resolve", builder, ""), }, }, { Use: "label", Short: "Manage a policy's labels", SubCommands: []*appcmd.Command{ policylabelarchive.NewCommand("archive", builder, ""), policylabelinfo.NewCommand("info", builder, ""), policylabellist.NewCommand("list", builder, ""), policylabelunarchive.NewCommand("unarchive", builder, ""), }, }, { Use: "settings", Short: "Manage a policy's settings", SubCommands: []*appcmd.Command{ policysettingsupdate.NewCommand("update", builder), }, }, policycreate.NewCommand("create", builder), policyinfo.NewCommand("info", builder), policydelete.NewCommand("delete", builder), }, }, }, }, { Use: "beta", Short: "Beta commands. Unstable and likely to change", SubCommands: []*appcmd.Command{ lspserve.NewCommand("lsp", builder, deprecatedMessage("buf lsp serve", "buf beta lsp"), true, true), price.NewCommand("price", builder), bufpluginv1beta1.NewCommand("buf-plugin-v1beta1", builder), bufpluginv1.NewCommand("buf-plugin-v1", builder), bufpluginv2.NewCommand("buf-plugin-v2", builder), studioagent.NewCommand("studio-agent", builder), { Use: "registry", Short: "Manage assets on the Buf Schema Registry", SubCommands: []*appcmd.Command{ { Use: "webhook", Short: "Manage webhooks for a repository on the Buf Schema Registry", SubCommands: []*appcmd.Command{ webhookcreate.NewCommand("create", builder), webhookdelete.NewCommand("delete", builder), webhooklist.NewCommand("list", builder), }, }, { Use: "plugin", Short: "Manage plugins on the Buf Schema Registry", SubCommands: []*appcmd.Command{ betapluginpush.NewCommand("push", builder), betaplugindelete.NewCommand("delete", builder), }, }, }, }, }, }, { Use: "alpha", Short: "Alpha commands. Unstable and recommended only for experimentation. These may be deleted", Hidden: true, SubCommands: []*appcmd.Command{ protoc.NewCommand("protoc", builder), { Use: "registry", Short: "Manage assets on the Buf Schema Registry", SubCommands: []*appcmd.Command{ { Use: "token", Short: "Manage user tokens", SubCommands: []*appcmd.Command{ tokenget.NewCommand("get", builder), tokenlist.NewCommand("list", builder), tokendelete.NewCommand("delete", builder), }, }, }, }, }, }, }, ModifyCobra: func(cobraCommand *cobra.Command) error { cobraCommand.AddCommand(bufcobra.NewWebpagesCommand("webpages", cobraCommand)) return nil }, } } // newErrorInterceptor returns a CLI interceptor that wraps Buf CLI errors. func newErrorInterceptor() appext.Interceptor { return func(next func(context.Context, appext.Container) error) func(context.Context, appext.Container) error { return func(ctx context.Context, container appext.Container) error { return wrapError(next(ctx, container)) } } } // wrapError is used when a CLI command fails, regardless of its error code. // Note that this function will wrap the error so that the underlying error // can be recovered via 'errors.Is'. func wrapError(err error) error { if err == nil { return nil } var connectErr *connect.Error isConnectError := errors.As(err, &connectErr) // If error is empty and not a system error or Connect error, we return it as-is. if !isConnectError && err.Error() == "" { return err } if isConnectError { var augmentedConnectError *bufconnect.AugmentedConnectError isAugmentedConnectErr := errors.As(err, &augmentedConnectError) if isPossibleNewCLIOldBSRError(connectErr) && isAugmentedConnectErr { return fmt.Errorf("Failure: %[1]s for https://%[2]s%[3]s\n"+ "This version of the buf CLI may require APIs that have not yet been deployed to https://%[2]s\n"+ "To resolve this failure, you can either:\n"+ "- Try using an older version of the buf CLI\n"+ "- Contact the site admin for https://%[2]s to upgrade the instance", connectErr, augmentedConnectError.Addr(), augmentedConnectError.Procedure(), ) } connectCode := connectErr.Code() switch { case connectCode == connect.CodeUnauthenticated, isEmptyUnknownError(err): loginCommand := "buf registry login" authErr, ok := bufconnect.AsAuthError(err) if !ok { // This code should be unreachable. return fmt.Errorf("Failure: you are not authenticated. "+ "Set the %[1]s environment variable or run %q, using a Buf API token as the password. "+ "If you have set the %[1]s or run the login command, "+ "your token may have expired. "+ "For details, visit https://buf.build/docs/bsr/authentication", bufconnect.TokenEnvKey, loginCommand, ) } // Invalid token found in env var. if authErr.HasToken() && authErr.TokenEnvKey() != "" { return fmt.Errorf("Failure: the %[1]s environment variable is not valid for %[2]s. "+ "Set %[1]s to a valid Buf API token, or unset it. "+ "For details, visit https://buf.build/docs/bsr/authentication", authErr.TokenEnvKey(), authErr.Remote(), ) } if authErr.Remote() != bufconnect.DefaultRemote { loginCommand = fmt.Sprintf("%s %s", loginCommand, authErr.Remote()) } // Invalid token found in netrc. if authErr.HasToken() { return fmt.Errorf("Failure: your Buf API token for %s is invalid. "+ "Run %q using a valid Buf API token. "+ "For details, visit https://buf.build/docs/bsr/authentication", authErr.Remote(), loginCommand, ) } // No token found. return fmt.Errorf("Failure: you are not authenticated for %s. "+ "Set the %s environment variable or run %q, "+ "using a Buf API token as the password. "+ "For details, visit https://buf.build/docs/bsr/authentication", authErr.Remote(), bufconnect.TokenEnvKey, loginCommand, ) case connectCode == connect.CodeUnavailable: msg := `Failure: the server hosted at that remote is unavailable.` // If the returned error is Unavailable, then determine if this is a DNS error. If so, // get the address used so that we can display a more helpful error message. if dnsError := (&net.DNSError{}); errors.As(err, &dnsError) && dnsError.IsNotFound { return fmt.Errorf(`%s Are you sure "%s" is a valid remote address?`, msg, dnsError.Name) } // If the unavailable error wraps a tls.CertificateVerificationError, show a more specific // error message to the user to aid in troubleshooting. if tlsErr := wrappedTLSError(err); tlsErr != nil { return fmt.Errorf("tls certificate verification: %w", tlsErr) } return errors.New(msg) } } sysError, isSysError := syserror.As(err) if isSysError { err = fmt.Errorf( "it looks like you have found a bug in buf. "+ "Please file an issue at https://github.com/bufbuild/buf/issues "+ "and provide the command you ran, as well as the following message: %w", sysError.Unwrap(), ) } var importNotExistError *bufmodule.ImportNotExistError if errors.As(err, &importNotExistError) { // There must be a better place to do this, perhaps in the Controller, but this works for now. err = app.WrapError(bufctl.ExitCodeFileAnnotation, importNotExistError) } return appFailureError(err) } // isEmptyUnknownError returns true if the given // error is non-nil, but has an empty message // and an unknown error code. // // This is relevant for errors returned by // envoyauthd when the client does not provide // an authentication header. func isEmptyUnknownError(err error) bool { if err == nil { return false } return err.Error() == "" && connect.CodeOf(err) == connect.CodeUnknown } // wrappedTLSError returns an unwrapped TLS error or nil if the error is another type of error. func wrappedTLSError(err error) error { if tlsErr := (&tls.CertificateVerificationError{}); errors.As(err, &tlsErr) { return tlsErr } return nil } func appFailureError(err error) error { return fmt.Errorf("Failure: %w", err) } // isPossibleNewCLIOldBSRError determines if an error might be from a newer // version of the CLI interacting with an older version of the BSR. func isPossibleNewCLIOldBSRError(connectErr *connect.Error) bool { switch connectErr.Code() { case connect.CodeUnknown: // Older versions of the BSR return errors of this shape // for unrecognized services. // NOTE: This handling can be removed once all BSR instances // are upgraded past v1.7.0. return connectErr.Message() == fmt.Sprintf("%d %s", http.StatusMethodNotAllowed, http.StatusText(http.StatusMethodNotAllowed)) case connect.CodeUnimplemented: // RPC was known, but unimplemented in the BSR version. return true default: return false } } // deprecatedMessage returns a message indicating that a command is deprecated. func deprecatedMessage(newCommand, oldCommand string) string { return fmt.Sprintf( `use "%s" instead. However, "%s" will continue to work.`, newCommand, oldCommand, ) } ================================================ FILE: cmd/buf/buf_test.go ================================================ // Copyright 2020-2026 Buf Technologies, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package main import ( "bytes" "encoding/json" "errors" "fmt" "io" "os" "path/filepath" "slices" "sort" "strconv" "strings" "testing" "buf.build/go/app/appcmd/appcmdtesting" "buf.build/go/bufplugin/check" "buf.build/go/standard/xslices" "github.com/bufbuild/buf/cmd/buf/internal/internaltesting" "github.com/bufbuild/buf/private/buf/bufcli" "github.com/bufbuild/buf/private/buf/bufctl" "github.com/bufbuild/buf/private/bufpkg/bufcheck" "github.com/bufbuild/buf/private/bufpkg/bufconfig" "github.com/bufbuild/buf/private/bufpkg/bufimage" imagev1 "github.com/bufbuild/buf/private/gen/proto/go/buf/alpha/image/v1" "github.com/bufbuild/buf/private/pkg/osext" "github.com/bufbuild/buf/private/pkg/protoencoding" "github.com/bufbuild/buf/private/pkg/slogtestext" "github.com/bufbuild/buf/private/pkg/storage/storageos" "github.com/bufbuild/buf/private/pkg/storage/storagetesting" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) var ( convertTestDataDir = filepath.Join("internal", "command", "convert", "testdata", "convert") // ordered, contains non-default builtinLintRulesV2 = []*outputCheckRule{ {ID: "DIRECTORY_SAME_PACKAGE", Categories: []string{"MINIMAL", "BASIC", "STANDARD"}, Default: true, Purpose: "Checks that all files in a given directory are in the same package."}, {ID: "PACKAGE_DEFINED", Categories: []string{"MINIMAL", "BASIC", "STANDARD"}, Default: true, Purpose: "Checks that all files have a package defined."}, {ID: "PACKAGE_DIRECTORY_MATCH", Categories: []string{"MINIMAL", "BASIC", "STANDARD"}, Default: true, Purpose: "Checks that all files are in a directory that matches their package name."}, {ID: "PACKAGE_NO_IMPORT_CYCLE", Categories: []string{"MINIMAL", "BASIC", "STANDARD"}, Default: true, Purpose: "Checks that packages do not have import cycles."}, {ID: "PACKAGE_SAME_DIRECTORY", Categories: []string{"MINIMAL", "BASIC", "STANDARD"}, Default: true, Purpose: "Checks that all files with a given package are in the same directory."}, {ID: "ENUM_FIRST_VALUE_ZERO", Categories: []string{"BASIC", "STANDARD"}, Default: true, Purpose: "Checks that all first values of enums have a numeric value of 0."}, {ID: "ENUM_NO_ALLOW_ALIAS", Categories: []string{"BASIC", "STANDARD"}, Default: true, Purpose: "Checks that enums do not have the allow_alias option set."}, {ID: "ENUM_PASCAL_CASE", Categories: []string{"BASIC", "STANDARD"}, Default: true, Purpose: "Checks that enums are PascalCase."}, {ID: "ENUM_VALUE_UPPER_SNAKE_CASE", Categories: []string{"BASIC", "STANDARD"}, Default: true, Purpose: "Checks that enum values are UPPER_SNAKE_CASE."}, {ID: "FIELD_LOWER_SNAKE_CASE", Categories: []string{"BASIC", "STANDARD"}, Default: true, Purpose: "Checks that field names are lower_snake_case."}, {ID: "FIELD_NOT_REQUIRED", Categories: []string{"BASIC", "STANDARD"}, Default: true, Purpose: "Checks that fields are not configured to be required."}, {ID: "IMPORT_NO_PUBLIC", Categories: []string{"BASIC", "STANDARD"}, Default: true, Purpose: "Checks that imports are not public."}, {ID: "IMPORT_USED", Categories: []string{"BASIC", "STANDARD"}, Default: true, Purpose: "Checks that imports are used."}, {ID: "MESSAGE_PASCAL_CASE", Categories: []string{"BASIC", "STANDARD"}, Default: true, Purpose: "Checks that messages are PascalCase."}, {ID: "ONEOF_LOWER_SNAKE_CASE", Categories: []string{"BASIC", "STANDARD"}, Default: true, Purpose: "Checks that oneof names are lower_snake_case."}, {ID: "PACKAGE_LOWER_SNAKE_CASE", Categories: []string{"BASIC", "STANDARD"}, Default: true, Purpose: "Checks that packages are lower_snake.case."}, {ID: "PACKAGE_SAME_CSHARP_NAMESPACE", Categories: []string{"BASIC", "STANDARD"}, Default: true, Purpose: "Checks that all files with a given package have the same value for the csharp_namespace option."}, {ID: "PACKAGE_SAME_GO_PACKAGE", Categories: []string{"BASIC", "STANDARD"}, Default: true, Purpose: "Checks that all files with a given package have the same value for the go_package option."}, {ID: "PACKAGE_SAME_JAVA_MULTIPLE_FILES", Categories: []string{"BASIC", "STANDARD"}, Default: true, Purpose: "Checks that all files with a given package have the same value for the java_multiple_files option."}, {ID: "PACKAGE_SAME_JAVA_PACKAGE", Categories: []string{"BASIC", "STANDARD"}, Default: true, Purpose: "Checks that all files with a given package have the same value for the java_package option."}, {ID: "PACKAGE_SAME_PHP_NAMESPACE", Categories: []string{"BASIC", "STANDARD"}, Default: true, Purpose: "Checks that all files with a given package have the same value for the php_namespace option."}, {ID: "PACKAGE_SAME_RUBY_PACKAGE", Categories: []string{"BASIC", "STANDARD"}, Default: true, Purpose: "Checks that all files with a given package have the same value for the ruby_package option."}, {ID: "PACKAGE_SAME_SWIFT_PREFIX", Categories: []string{"BASIC", "STANDARD"}, Default: true, Purpose: "Checks that all files with a given package have the same value for the swift_prefix option."}, {ID: "RPC_PASCAL_CASE", Categories: []string{"BASIC", "STANDARD"}, Default: true, Purpose: "Checks that RPCs are PascalCase."}, {ID: "SERVICE_PASCAL_CASE", Categories: []string{"BASIC", "STANDARD"}, Default: true, Purpose: "Checks that services are PascalCase."}, {ID: "SYNTAX_SPECIFIED", Categories: []string{"BASIC", "STANDARD"}, Default: true, Purpose: "Checks that all files have a syntax specified."}, {ID: "ENUM_VALUE_PREFIX", Categories: []string{"STANDARD"}, Default: true, Purpose: "Checks that enum values are prefixed with ENUM_NAME_UPPER_SNAKE_CASE."}, {ID: "ENUM_ZERO_VALUE_SUFFIX", Categories: []string{"STANDARD"}, Default: true, Purpose: "Checks that enum zero values have a consistent suffix (configurable, default suffix is \"_UNSPECIFIED\")."}, {ID: "FILE_LOWER_SNAKE_CASE", Categories: []string{"STANDARD"}, Default: true, Purpose: "Checks that filenames are lower_snake_case."}, {ID: "PACKAGE_VERSION_SUFFIX", Categories: []string{"STANDARD"}, Default: true, Purpose: "Checks that the last component of all packages is a version of the form v\\d+, v\\d+test.*, v\\d+(alpha|beta)\\d+, or v\\d+p\\d+(alpha|beta)\\d+, where numbers are >=1."}, {ID: "PROTOVALIDATE", Categories: []string{"STANDARD"}, Default: true, Purpose: "Checks that protovalidate rules are valid and all CEL expressions compile."}, {ID: "RPC_REQUEST_RESPONSE_UNIQUE", Categories: []string{"STANDARD"}, Default: true, Purpose: "Checks that RPC request and response types are only used in one RPC (configurable)."}, {ID: "RPC_REQUEST_STANDARD_NAME", Categories: []string{"STANDARD"}, Default: true, Purpose: "Checks that RPC request type names are RPCNameRequest or ServiceNameRPCNameRequest (configurable)."}, {ID: "RPC_RESPONSE_STANDARD_NAME", Categories: []string{"STANDARD"}, Default: true, Purpose: "Checks that RPC response type names are RPCNameResponse or ServiceNameRPCNameResponse (configurable)."}, {ID: "SERVICE_SUFFIX", Categories: []string{"STANDARD"}, Default: true, Purpose: "Checks that services have a consistent suffix (configurable, default suffix is \"Service\")."}, {ID: "COMMENT_ENUM", Categories: []string{"COMMENTS"}, Default: false, Purpose: "Checks that enums have non-empty comments."}, {ID: "COMMENT_ENUM_VALUE", Categories: []string{"COMMENTS"}, Default: false, Purpose: "Checks that enum values have non-empty comments."}, {ID: "COMMENT_FIELD", Categories: []string{"COMMENTS"}, Default: false, Purpose: "Checks that fields have non-empty comments."}, {ID: "COMMENT_MESSAGE", Categories: []string{"COMMENTS"}, Default: false, Purpose: "Checks that messages have non-empty comments."}, {ID: "COMMENT_ONEOF", Categories: []string{"COMMENTS"}, Default: false, Purpose: "Checks that oneofs have non-empty comments."}, {ID: "COMMENT_RPC", Categories: []string{"COMMENTS"}, Default: false, Purpose: "Checks that RPCs have non-empty comments."}, {ID: "COMMENT_SERVICE", Categories: []string{"COMMENTS"}, Default: false, Purpose: "Checks that services have non-empty comments."}, {ID: "RPC_NO_CLIENT_STREAMING", Categories: []string{"UNARY_RPC"}, Default: false, Purpose: "Checks that RPCs are not client streaming."}, {ID: "RPC_NO_SERVER_STREAMING", Categories: []string{"UNARY_RPC"}, Default: false, Purpose: "Checks that RPCs are not server streaming."}, {ID: "STABLE_PACKAGE_NO_IMPORT_UNSTABLE", Categories: []string{}, Default: false, Purpose: "Checks that all files that have stable versioned packages do not import packages with unstable version packages."}, } // ordered, contains non-default builtinBreakingRulesV2 = []*outputCheckRule{ {ID: "EXTENSION_NO_DELETE", Categories: []string{"FILE"}, Default: true, Purpose: "Checks that extensions are not deleted from a given file."}, {ID: "SERVICE_NO_DELETE", Categories: []string{"FILE"}, Default: true, Purpose: "Checks that services are not deleted from a given file."}, {ID: "ENUM_NO_DELETE", Categories: []string{"CSR", "FILE"}, Default: true, Purpose: "Checks that enums are not deleted from a given file."}, {ID: "FILE_NO_DELETE", Categories: []string{"CSR", "FILE"}, Default: true, Purpose: "Checks that files are not deleted."}, {ID: "MESSAGE_NO_DELETE", Categories: []string{"CSR", "FILE"}, Default: true, Purpose: "Checks that messages are not deleted from a given file."}, {ID: "ENUM_SAME_TYPE", Categories: []string{"FILE", "PACKAGE"}, Default: true, Purpose: "Checks that enums have the same type (open vs closed)."}, {ID: "FIELD_SAME_CARDINALITY", Categories: []string{"FILE", "PACKAGE"}, Default: true, Purpose: "Checks that fields have the same cardinalities in a given message."}, {ID: "FIELD_SAME_CPP_STRING_TYPE", Categories: []string{"FILE", "PACKAGE"}, Default: true, Purpose: "Checks that fields have the same C++ string type, based on ctype field option or (pb.cpp).string_type feature."}, {ID: "FIELD_SAME_JAVA_UTF8_VALIDATION", Categories: []string{"FILE", "PACKAGE"}, Default: true, Purpose: "Checks that fields have the same Java string UTF8 validation, based on java_string_check_utf8 file option or (pb.java).utf8_validation feature."}, {ID: "FIELD_SAME_JSTYPE", Categories: []string{"FILE", "PACKAGE"}, Default: true, Purpose: "Checks that fields have the same value for the jstype option."}, {ID: "FIELD_SAME_UTF8_VALIDATION", Categories: []string{"FILE", "PACKAGE"}, Default: true, Purpose: "Checks that string fields have the same UTF8 validation mode."}, {ID: "FILE_SAME_CC_ENABLE_ARENAS", Categories: []string{"FILE", "PACKAGE"}, Default: true, Purpose: "Checks that files have the same value for the cc_enable_arenas option."}, {ID: "FILE_SAME_CC_GENERIC_SERVICES", Categories: []string{"FILE", "PACKAGE"}, Default: true, Purpose: "Checks that files have the same value for the cc_generic_services option."}, {ID: "FILE_SAME_CSHARP_NAMESPACE", Categories: []string{"FILE", "PACKAGE"}, Default: true, Purpose: "Checks that files have the same value for the csharp_namespace option."}, {ID: "FILE_SAME_GO_PACKAGE", Categories: []string{"FILE", "PACKAGE"}, Default: true, Purpose: "Checks that files have the same value for the go_package option."}, {ID: "FILE_SAME_JAVA_GENERIC_SERVICES", Categories: []string{"FILE", "PACKAGE"}, Default: true, Purpose: "Checks that files have the same value for the java_generic_services option."}, {ID: "FILE_SAME_JAVA_MULTIPLE_FILES", Categories: []string{"FILE", "PACKAGE"}, Default: true, Purpose: "Checks that files have the same value for the java_multiple_files option."}, {ID: "FILE_SAME_JAVA_OUTER_CLASSNAME", Categories: []string{"FILE", "PACKAGE"}, Default: true, Purpose: "Checks that files have the same value for the java_outer_classname option."}, {ID: "FILE_SAME_JAVA_PACKAGE", Categories: []string{"FILE", "PACKAGE"}, Default: true, Purpose: "Checks that files have the same value for the java_package option."}, {ID: "FILE_SAME_OBJC_CLASS_PREFIX", Categories: []string{"FILE", "PACKAGE"}, Default: true, Purpose: "Checks that files have the same value for the objc_class_prefix option."}, {ID: "FILE_SAME_OPTIMIZE_FOR", Categories: []string{"FILE", "PACKAGE"}, Default: true, Purpose: "Checks that files have the same value for the optimize_for option."}, {ID: "FILE_SAME_PHP_CLASS_PREFIX", Categories: []string{"FILE", "PACKAGE"}, Default: true, Purpose: "Checks that files have the same value for the php_class_prefix option."}, {ID: "FILE_SAME_PHP_METADATA_NAMESPACE", Categories: []string{"FILE", "PACKAGE"}, Default: true, Purpose: "Checks that files have the same value for the php_metadata_namespace option."}, {ID: "FILE_SAME_PHP_NAMESPACE", Categories: []string{"FILE", "PACKAGE"}, Default: true, Purpose: "Checks that files have the same value for the php_namespace option."}, {ID: "FILE_SAME_PY_GENERIC_SERVICES", Categories: []string{"FILE", "PACKAGE"}, Default: true, Purpose: "Checks that files have the same value for the py_generic_services option."}, {ID: "FILE_SAME_RUBY_PACKAGE", Categories: []string{"FILE", "PACKAGE"}, Default: true, Purpose: "Checks that files have the same value for the ruby_package option."}, {ID: "FILE_SAME_SWIFT_PREFIX", Categories: []string{"FILE", "PACKAGE"}, Default: true, Purpose: "Checks that files have the same value for the swift_prefix option."}, {ID: "RPC_NO_DELETE", Categories: []string{"FILE", "PACKAGE"}, Default: true, Purpose: "Checks that rpcs are not deleted from a given service."}, {ID: "ENUM_VALUE_NO_DELETE", Categories: []string{"CSR", "FILE", "PACKAGE"}, Default: true, Purpose: "Checks that enum values are not deleted from a given enum."}, {ID: "EXTENSION_MESSAGE_NO_DELETE", Categories: []string{"CSR", "FILE", "PACKAGE"}, Default: true, Purpose: "Checks that extension ranges are not deleted from a given message."}, {ID: "FIELD_NO_DELETE", Categories: []string{"CSR", "FILE", "PACKAGE"}, Default: true, Purpose: "Checks that fields are not deleted from a given message."}, {ID: "FIELD_SAME_TYPE", Categories: []string{"CSR", "FILE", "PACKAGE"}, Default: true, Purpose: "Checks that fields have the same types in a given message."}, {ID: "FILE_SAME_SYNTAX", Categories: []string{"CSR", "FILE", "PACKAGE"}, Default: true, Purpose: "Checks that files have the same syntax."}, {ID: "MESSAGE_NO_REMOVE_STANDARD_DESCRIPTOR_ACCESSOR", Categories: []string{"CSR", "FILE", "PACKAGE"}, Default: true, Purpose: "Checks that messages do not change the no_standard_descriptor_accessor option from false or unset to true."}, {ID: "ONEOF_NO_DELETE", Categories: []string{"CSR", "FILE", "PACKAGE"}, Default: true, Purpose: "Checks that oneofs are not deleted from a given message."}, {ID: "ENUM_SAME_JSON_FORMAT", Categories: []string{"CSR", "FILE", "PACKAGE", "WIRE_JSON"}, Default: true, Purpose: "Checks that enums have the same JSON format support."}, {ID: "ENUM_VALUE_SAME_NAME", Categories: []string{"CSR", "FILE", "PACKAGE", "WIRE_JSON"}, Default: true, Purpose: "Checks that enum values have the same name."}, {ID: "FIELD_SAME_JSON_NAME", Categories: []string{"CSR", "FILE", "PACKAGE", "WIRE_JSON"}, Default: true, Purpose: "Checks that fields have the same value for the json_name option."}, {ID: "FIELD_SAME_NAME", Categories: []string{"CSR", "FILE", "PACKAGE", "WIRE_JSON"}, Default: true, Purpose: "Checks that fields have the same names in a given message."}, {ID: "MESSAGE_SAME_JSON_FORMAT", Categories: []string{"CSR", "FILE", "PACKAGE", "WIRE_JSON"}, Default: true, Purpose: "Checks that messages have the same JSON format support."}, {ID: "FIELD_SAME_DEFAULT", Categories: []string{"CSR", "FILE", "PACKAGE", "WIRE_JSON", "WIRE"}, Default: true, Purpose: "Checks that fields have the same default value, if a default is specified."}, {ID: "FIELD_SAME_ONEOF", Categories: []string{"CSR", "FILE", "PACKAGE", "WIRE_JSON", "WIRE"}, Default: true, Purpose: "Checks that fields have the same oneofs in a given message."}, {ID: "FILE_SAME_PACKAGE", Categories: []string{"CSR", "FILE", "PACKAGE", "WIRE_JSON", "WIRE"}, Default: true, Purpose: "Checks that files have the same package."}, {ID: "MESSAGE_SAME_REQUIRED_FIELDS", Categories: []string{"CSR", "FILE", "PACKAGE", "WIRE_JSON", "WIRE"}, Default: true, Purpose: "Checks that messages have no added or deleted required fields."}, {ID: "RESERVED_ENUM_NO_DELETE", Categories: []string{"CSR", "FILE", "PACKAGE", "WIRE_JSON", "WIRE"}, Default: true, Purpose: "Checks that reserved ranges and names are not deleted from a given enum."}, {ID: "RESERVED_MESSAGE_NO_DELETE", Categories: []string{"CSR", "FILE", "PACKAGE", "WIRE_JSON", "WIRE"}, Default: true, Purpose: "Checks that reserved ranges and names are not deleted from a given message."}, {ID: "RPC_SAME_CLIENT_STREAMING", Categories: []string{"CSR", "FILE", "PACKAGE", "WIRE_JSON", "WIRE"}, Default: true, Purpose: "Checks that rpcs have the same client streaming value."}, {ID: "RPC_SAME_IDEMPOTENCY_LEVEL", Categories: []string{"CSR", "FILE", "PACKAGE", "WIRE_JSON", "WIRE"}, Default: true, Purpose: "Checks that rpcs have the same value for the idempotency_level option."}, {ID: "RPC_SAME_REQUEST_TYPE", Categories: []string{"CSR", "FILE", "PACKAGE", "WIRE_JSON", "WIRE"}, Default: true, Purpose: "Checks that rpcs are have the same request type."}, {ID: "RPC_SAME_RESPONSE_TYPE", Categories: []string{"CSR", "FILE", "PACKAGE", "WIRE_JSON", "WIRE"}, Default: true, Purpose: "Checks that rpcs are have the same response type."}, {ID: "RPC_SAME_SERVER_STREAMING", Categories: []string{"CSR", "FILE", "PACKAGE", "WIRE_JSON", "WIRE"}, Default: true, Purpose: "Checks that rpcs have the same server streaming value."}, {ID: "PACKAGE_ENUM_NO_DELETE", Categories: []string{"PACKAGE"}, Default: false, Purpose: "Checks that enums are not deleted from a given package."}, {ID: "PACKAGE_EXTENSION_NO_DELETE", Categories: []string{"PACKAGE"}, Default: false, Purpose: "Checks that extensions are not deleted from a given package."}, {ID: "PACKAGE_MESSAGE_NO_DELETE", Categories: []string{"PACKAGE"}, Default: false, Purpose: "Checks that messages are not deleted from a given package."}, {ID: "PACKAGE_NO_DELETE", Categories: []string{"PACKAGE"}, Default: false, Purpose: "Checks that packages are not deleted."}, {ID: "PACKAGE_SERVICE_NO_DELETE", Categories: []string{"PACKAGE"}, Default: false, Purpose: "Checks that services are not deleted from a given package."}, {ID: "ENUM_VALUE_NO_DELETE_UNLESS_NAME_RESERVED", Categories: []string{"CSR", "WIRE_JSON"}, Default: false, Purpose: "Checks that enum values are not deleted from a given enum unless the name is reserved."}, {ID: "FIELD_NO_DELETE_UNLESS_NAME_RESERVED", Categories: []string{"CSR", "WIRE_JSON"}, Default: false, Purpose: "Checks that fields are not deleted from a given message unless the name is reserved."}, {ID: "FIELD_WIRE_JSON_COMPATIBLE_CARDINALITY", Categories: []string{"CSR", "WIRE_JSON"}, Default: false, Purpose: "Checks that fields have wire and JSON compatible cardinalities in a given message."}, {ID: "FIELD_WIRE_JSON_COMPATIBLE_TYPE", Categories: []string{"CSR", "WIRE_JSON"}, Default: false, Purpose: "Checks that fields have wire and JSON compatible types in a given message."}, {ID: "ENUM_VALUE_NO_DELETE_UNLESS_NUMBER_RESERVED", Categories: []string{"CSR", "WIRE_JSON", "WIRE"}, Default: false, Purpose: "Checks that enum values are not deleted from a given enum unless the number is reserved."}, {ID: "FIELD_NO_DELETE_UNLESS_NUMBER_RESERVED", Categories: []string{"CSR", "WIRE_JSON", "WIRE"}, Default: false, Purpose: "Checks that fields are not deleted from a given message unless the number is reserved."}, {ID: "FIELD_WIRE_COMPATIBLE_CARDINALITY", Categories: []string{"WIRE"}, Default: false, Purpose: "Checks that fields have wire-compatible cardinalities in a given message."}, {ID: "FIELD_WIRE_COMPATIBLE_TYPE", Categories: []string{"WIRE"}, Default: false, Purpose: "Checks that fields have wire-compatible types in a given message."}, } ) type outputCheckRule struct { ID string `json:"id"` Categories []string `json:"categories"` Default bool `json:"default"` Purpose string `json:"purpose"` Plugin string `json:"plugin"` Deprecated bool `json:"deprecated"` Replacements []string `json:"replacements"` } func TestSuccess1(t *testing.T) { t.Parallel() testRunStdout(t, nil, 1, ``, "build", "--source", filepath.Join("testdata", "success")) testRunStdout(t, nil, 0, ``, "build", filepath.Join("testdata", "success")) } func TestSuccess2(t *testing.T) { t.Parallel() testRunStdout(t, nil, 1, ``, "build", "--exclude-imports", "--source", filepath.Join("testdata", "success")) testRunStdout(t, nil, 0, ``, "build", "--exclude-imports", filepath.Join("testdata", "success")) } func TestSuccess3(t *testing.T) { t.Parallel() testRunStdout(t, nil, 1, ``, "build", "--exclude-source-info", "--source", filepath.Join("testdata", "success")) testRunStdout(t, nil, 0, ``, "build", "--exclude-source-info", filepath.Join("testdata", "success")) } func TestSuccess4(t *testing.T) { t.Parallel() testRunStdout(t, nil, 1, ``, "build", "--exclude-imports", "--exclude-source-info", "--source", filepath.Join("testdata", "success")) testRunStdout(t, nil, 0, ``, "build", "--exclude-imports", "--exclude-source-info", filepath.Join("testdata", "success")) } func TestSuccess5(t *testing.T) { t.Parallel() testRunStdout(t, nil, 1, ``, "build", "--exclude-imports", "--exclude-source-info", "--source", filepath.Join("testdata", "success")) testRunStdout(t, nil, 0, ``, "build", "--exclude-imports", "--exclude-source-info", filepath.Join("testdata", "success")) } func TestSuccess6(t *testing.T) { t.Parallel() testRunStdout(t, nil, 0, ``, "lint", filepath.Join("testdata", "success")) testRunStdout(t, nil, 1, ``, "lint", "--input", filepath.Join("testdata", "success", "buf", "buf.proto")) testRunStdout(t, nil, 0, ``, "lint", filepath.Join("testdata", "success", "buf", "buf.proto")) } func TestSuccessDir(t *testing.T) { t.Parallel() testRunStdout(t, nil, 0, ``, "build", filepath.Join("testdata", "successnobufyaml")) testRunStdout( t, nil, 0, ``, "build", filepath.Join("testdata", "successnobufyaml"), "--path", filepath.Join("testdata", "successnobufyaml", "buf", "buf.proto"), ) wd, err := osext.Getwd() require.NoError(t, err) testRunStdout(t, nil, 0, ``, "build", filepath.Join(wd, "testdata", "successnobufyaml")) testRunStdout( t, nil, 0, ``, "build", filepath.Join(wd, "testdata", "successnobufyaml"), "--path", filepath.Join(wd, "testdata", "successnobufyaml", "buf", "buf.proto"), ) } func TestFail1(t *testing.T) { t.Parallel() testRunStdout( t, nil, 1, ``, "build", "--source", filepath.Join("testdata", "fail"), ) testRunStdout( t, nil, 0, ``, "build", filepath.Join("testdata", "fail"), ) } func TestFail2(t *testing.T) { t.Parallel() testRunStdout( t, nil, 1, ``, "build", "--exclude-imports", "--source", filepath.Join("testdata", "fail"), ) testRunStdout( t, nil, 0, ``, "build", "--exclude-imports", filepath.Join("testdata", "fail"), ) } func TestFail3(t *testing.T) { t.Parallel() testRunStdout( t, nil, 1, ``, "build", "--exclude-source-info", "--source", filepath.Join("testdata", "fail"), ) testRunStdout( t, nil, 0, ``, "build", "--exclude-source-info", filepath.Join("testdata", "fail"), ) } func TestFail4(t *testing.T) { t.Parallel() testRunStdout( t, nil, 1, ``, "build", "--exclude-imports", "--exclude-source-info", "--source", filepath.Join("testdata", "fail"), ) testRunStdout( t, nil, 0, ``, "build", "--exclude-imports", "--exclude-source-info", filepath.Join("testdata", "fail"), ) } func TestFail5(t *testing.T) { t.Parallel() testRunStdout( t, nil, bufctl.ExitCodeFileAnnotation, filepath.FromSlash(`testdata/fail/buf/buf.proto:3:1:Files with package "other" must be within a directory "other" relative to root but were in directory "buf". testdata/fail/buf/buf.proto:6:9:Field name "oneTwo" should be lower_snake_case, such as "one_two".`), "lint", filepath.Join("testdata", "fail"), ) testRunStdout( t, nil, 1, ``, "lint", "--input", filepath.Join("testdata", "fail", "buf", "buf.proto"), ) testRunStdout( t, nil, bufctl.ExitCodeFileAnnotation, filepath.FromSlash(`testdata/fail/buf/buf.proto:3:1:Files with package "other" must be within a directory "other" relative to root but were in directory "buf". testdata/fail/buf/buf.proto:6:9:Field name "oneTwo" should be lower_snake_case, such as "one_two".`), "lint", filepath.Join("testdata", "fail", "buf", "buf.proto"), ) } func TestFail6(t *testing.T) { t.Parallel() testRunStdoutStderrNoWarn( t, nil, bufctl.ExitCodeFileAnnotation, filepath.FromSlash(`testdata/fail/buf/buf.proto:3:1:Files with package "other" must be within a directory "other" relative to root but were in directory "buf". testdata/fail/buf/buf.proto:6:9:Field name "oneTwo" should be lower_snake_case, such as "one_two".`), "", // stderr should be empty "lint", filepath.Join("testdata", "fail"), "--path", filepath.Join("testdata", "fail", "buf", "buf.proto"), ) testRunStdout( t, nil, 1, ``, "lint", "--input", filepath.Join("testdata", "fail", "buf", "buf.proto"), "--path", filepath.Join("testdata", "fail", "buf", "buf.proto"), ) testRunStdoutStderrNoWarn( t, nil, 1, "", // stdout should be empty "Failure: --path is not valid for use with .proto file references", "lint", filepath.Join("testdata", "fail", "buf", "buf.proto"), "--path", filepath.Join("testdata", "fail", "buf", "buf.proto"), ) } func TestFail7(t *testing.T) { t.Parallel() testRunStdout( t, nil, 1, ``, "lint", filepath.Join("testdata", "fail", "buf", "buf.proto"), "--input-config", `{"version":"v1","lint":{"use":["BASIC"]}}`, ) testRunStdoutStderrNoWarn( t, nil, bufctl.ExitCodeFileAnnotation, filepath.FromSlash(`testdata/fail/buf/buf.proto:3:1:Files with package "other" must be within a directory "other" relative to root but were in directory "fail/buf". testdata/fail/buf/buf.proto:6:9:Field name "oneTwo" should be lower_snake_case, such as "one_two".`), // This is new behavior we introduced. When setting a config override, we no longer do // a search for the controlling workspace. See bufctl/option.go for additional details. // Only the paths specified by "--path" in the command are considered. This avoids build // failures from other proto files under testdata. Command "build" succeeds with this path // restriction, "lint" should be able to build the image and only fail on lint issue for // the specified file. "", "lint", "--path", filepath.Join("testdata", "fail", "buf", "buf.proto"), filepath.Join("testdata"), "--config", `{"version":"v1beta1","lint":{"use":["BASIC"]}}`, ) testRunStdoutStderrNoWarn( t, nil, bufctl.ExitCodeFileAnnotation, // Note: `were in directory "buf"` was changed to `were in directory "testdata/fail/buf"` // during the refactor. This is actually more correct - pre-refactor, the CLI was acting // as if the buf.yaml at testdata/fail/buf.yaml mattered in some way. In fact, it doesn't - // you've said that you have overridden it entirely. filepath.FromSlash(`testdata/fail/buf/buf.proto:3:1:Files with package "other" must be within a directory "other" relative to root but were in directory ".". testdata/fail/buf/buf.proto:6:9:Field name "oneTwo" should be lower_snake_case, such as "one_two".`), "", // stderr should be empty "lint", filepath.Join("testdata", "fail", "buf", "buf.proto"), "--config", `{"version":"v1","lint":{"use":["BASIC"]}}`, ) } func TestFail8(t *testing.T) { t.Parallel() testRunStdout( t, nil, bufctl.ExitCodeFileAnnotation, filepath.FromSlash(`testdata/fail2/buf/buf.proto:6:9:Field name "oneTwo" should be lower_snake_case, such as "one_two". testdata/fail2/buf/buf2.proto:9:9:Field name "oneThree" should be lower_snake_case, such as "one_three".`), "lint", filepath.Join("testdata", "fail2"), ) } func TestFail9(t *testing.T) { t.Parallel() testRunStdout( t, nil, bufctl.ExitCodeFileAnnotation, filepath.FromSlash(`testdata/fail2/buf/buf.proto:6:9:Field name "oneTwo" should be lower_snake_case, such as "one_two".`), "lint", filepath.Join("testdata", "fail2"), "--path", filepath.Join("testdata", "fail2", "buf", "buf.proto"), ) } func TestFail10(t *testing.T) { t.Parallel() testRunStdout( t, nil, 0, ``, "lint", filepath.Join("testdata", "fail2"), "--path", filepath.Join("testdata", "fail2", "buf", "buf3.proto"), ) testRunStdout( t, nil, 0, "", "lint", filepath.Join("testdata", "fail2", "buf", "buf3.proto"), ) } func TestFail11(t *testing.T) { t.Parallel() testRunStdout( t, nil, 1, ``, "lint", "--path", filepath.Join("testdata", "fail2", "buf", "buf2.proto"), "--input", filepath.Join("testdata"), ) testRunStdout( t, nil, bufctl.ExitCodeFileAnnotation, filepath.FromSlash(`testdata/fail2/buf/buf2.proto:9:9:Field name "oneThree" should be lower_snake_case, such as "one_three".`), "lint", "--path", filepath.Join("testdata", "fail2", "buf", "buf2.proto"), filepath.Join("testdata"), ) testRunStdout( t, nil, bufctl.ExitCodeFileAnnotation, filepath.FromSlash(`testdata/fail2/buf/buf2.proto:9:9:Field name "oneThree" should be lower_snake_case, such as "one_three".`), "lint", filepath.Join("testdata", "fail2", "buf", "buf2.proto"), ) } func TestFail12(t *testing.T) { t.Parallel() testRunStdout( t, nil, bufctl.ExitCodeFileAnnotation, `version: v1 lint: ignore_only: FIELD_LOWER_SNAKE_CASE: - buf/buf.proto PACKAGE_DIRECTORY_MATCH: - buf/buf.proto`, "lint", filepath.Join("testdata", "fail"), "--error-format", "config-ignore-yaml", ) } func TestFail13(t *testing.T) { t.Parallel() // this tests that we still use buf.mod if it exists // this has an ignore for FIELD_LOWER_SNAKE_CASE to make sure we are actually reading the config testRunStdout( t, nil, bufctl.ExitCodeFileAnnotation, filepath.FromSlash(`testdata/fail_buf_mod/buf/buf.proto:3:1:Files with package "other" must be within a directory "other" relative to root but were in directory "buf".`), "lint", filepath.Join("testdata", "fail_buf_mod"), ) } func TestFailCheckBreaking1(t *testing.T) { t.Parallel() testRunStdoutStderrNoWarn( t, nil, bufctl.ExitCodeFileAnnotation, filepath.FromSlash(` ../../private/bufpkg/bufcheck/testdata/breaking/current/breaking_field_no_delete/1.proto:5:1:Previously present field "3" with name "three" on message "Two" was deleted. ../../private/bufpkg/bufcheck/testdata/breaking/current/breaking_field_no_delete/1.proto:10:1:Previously present field "3" with name "three" on message "Three" was deleted. ../../private/bufpkg/bufcheck/testdata/breaking/current/breaking_field_no_delete/1.proto:12:5:Previously present field "3" with name "three" on message "Five" was deleted. ../../private/bufpkg/bufcheck/testdata/breaking/current/breaking_field_no_delete/1.proto:22:3:Previously present field "3" with name "three" on message "Seven" was deleted. ../../private/bufpkg/bufcheck/testdata/breaking/current/breaking_field_no_delete/2.proto:57:1:Previously present field "3" with name "three" on message "Nine" was deleted. `), "", // stderr should be empty "breaking", // can't bother right now to filepath.Join this "../../private/bufpkg/bufcheck/testdata/breaking/current/breaking_field_no_delete", "--against", "../../private/bufpkg/bufcheck/testdata/breaking/previous/breaking_field_no_delete", ) } func TestFailCheckBreaking2(t *testing.T) { t.Parallel() testRunStdout( t, nil, bufctl.ExitCodeFileAnnotation, filepath.FromSlash(`testdata/protofileref/breaking/a/foo.proto:7:3:Field "2" with name "world" on message "Foo" changed type from "int32" to "string".`), "breaking", filepath.Join("testdata", "protofileref", "breaking", "a", "foo.proto"), "--against", filepath.Join("testdata", "protofileref", "breaking", "b", "foo.proto"), ) } func TestFailCheckBreaking3(t *testing.T) { t.Parallel() testRunStdout( t, nil, bufctl.ExitCodeFileAnnotation, filepath.FromSlash(` :1:1:Previously present file "bar.proto" was deleted. testdata/protofileref/breaking/a/foo.proto:7:3:Field "2" with name "world" on message "Foo" changed type from "int32" to "string". `), "breaking", filepath.Join("testdata", "protofileref", "breaking", "a", "foo.proto"), "--against", filepath.Join("testdata", "protofileref", "breaking", "b"), ) } func TestFailCheckBreaking4(t *testing.T) { t.Parallel() testRunStdout( t, nil, bufctl.ExitCodeFileAnnotation, filepath.FromSlash(` testdata/protofileref/breaking/a/bar.proto:5:1:Previously present field "2" with name "value" on message "Bar" was deleted. testdata/protofileref/breaking/a/foo.proto:7:3:Field "2" with name "world" on message "Foo" changed type from "int32" to "string". `), "breaking", fmt.Sprintf("%s#include_package_files=true", filepath.Join("testdata", "protofileref", "breaking", "a", "foo.proto")), "--against", filepath.Join("testdata", "protofileref", "breaking", "b"), ) } func TestFailCheckBreaking5(t *testing.T) { t.Parallel() testRunStdout( t, nil, bufctl.ExitCodeFileAnnotation, filepath.FromSlash(` :1:1:Previously present file "bar.proto" was deleted. testdata/protofileref/breaking/a/foo.proto:7:3:Field "2" with name "world" on message "Foo" changed type from "int32" to "string". `), "breaking", filepath.Join("testdata", "protofileref", "breaking", "a", "foo.proto"), "--against", fmt.Sprintf("%s#include_package_files=true", filepath.Join("testdata", "protofileref", "breaking", "b", "foo.proto")), ) } func TestCheckLsLintRulesModAll(t *testing.T) { t.Parallel() expectedStdout := ` ID CATEGORIES DEFAULT PURPOSE DIRECTORY_SAME_PACKAGE MINIMAL, BASIC, STANDARD * Checks that all files in a given directory are in the same package. PACKAGE_DEFINED MINIMAL, BASIC, STANDARD * Checks that all files have a package defined. PACKAGE_DIRECTORY_MATCH MINIMAL, BASIC, STANDARD * Checks that all files are in a directory that matches their package name. PACKAGE_SAME_DIRECTORY MINIMAL, BASIC, STANDARD * Checks that all files with a given package are in the same directory. ENUM_FIRST_VALUE_ZERO BASIC, STANDARD * Checks that all first values of enums have a numeric value of 0. ENUM_NO_ALLOW_ALIAS BASIC, STANDARD * Checks that enums do not have the allow_alias option set. ENUM_PASCAL_CASE BASIC, STANDARD * Checks that enums are PascalCase. ENUM_VALUE_UPPER_SNAKE_CASE BASIC, STANDARD * Checks that enum values are UPPER_SNAKE_CASE. FIELD_LOWER_SNAKE_CASE BASIC, STANDARD * Checks that field names are lower_snake_case. IMPORT_NO_PUBLIC BASIC, STANDARD * Checks that imports are not public. IMPORT_USED BASIC, STANDARD * Checks that imports are used. MESSAGE_PASCAL_CASE BASIC, STANDARD * Checks that messages are PascalCase. ONEOF_LOWER_SNAKE_CASE BASIC, STANDARD * Checks that oneof names are lower_snake_case. PACKAGE_LOWER_SNAKE_CASE BASIC, STANDARD * Checks that packages are lower_snake.case. PACKAGE_SAME_CSHARP_NAMESPACE BASIC, STANDARD * Checks that all files with a given package have the same value for the csharp_namespace option. PACKAGE_SAME_GO_PACKAGE BASIC, STANDARD * Checks that all files with a given package have the same value for the go_package option. PACKAGE_SAME_JAVA_MULTIPLE_FILES BASIC, STANDARD * Checks that all files with a given package have the same value for the java_multiple_files option. PACKAGE_SAME_JAVA_PACKAGE BASIC, STANDARD * Checks that all files with a given package have the same value for the java_package option. PACKAGE_SAME_PHP_NAMESPACE BASIC, STANDARD * Checks that all files with a given package have the same value for the php_namespace option. PACKAGE_SAME_RUBY_PACKAGE BASIC, STANDARD * Checks that all files with a given package have the same value for the ruby_package option. PACKAGE_SAME_SWIFT_PREFIX BASIC, STANDARD * Checks that all files with a given package have the same value for the swift_prefix option. RPC_PASCAL_CASE BASIC, STANDARD * Checks that RPCs are PascalCase. SERVICE_PASCAL_CASE BASIC, STANDARD * Checks that services are PascalCase. SYNTAX_SPECIFIED BASIC, STANDARD * Checks that all files have a syntax specified. ENUM_VALUE_PREFIX STANDARD * Checks that enum values are prefixed with ENUM_NAME_UPPER_SNAKE_CASE. ENUM_ZERO_VALUE_SUFFIX STANDARD * Checks that enum zero values have a consistent suffix (configurable, default suffix is "_UNSPECIFIED"). FILE_LOWER_SNAKE_CASE STANDARD * Checks that filenames are lower_snake_case. PACKAGE_VERSION_SUFFIX STANDARD * Checks that the last component of all packages is a version of the form v\d+, v\d+test.*, v\d+(alpha|beta)\d+, or v\d+p\d+(alpha|beta)\d+, where numbers are >=1. PROTOVALIDATE STANDARD * Checks that protovalidate rules are valid and all CEL expressions compile. RPC_REQUEST_RESPONSE_UNIQUE STANDARD * Checks that RPC request and response types are only used in one RPC (configurable). RPC_REQUEST_STANDARD_NAME STANDARD * Checks that RPC request type names are RPCNameRequest or ServiceNameRPCNameRequest (configurable). RPC_RESPONSE_STANDARD_NAME STANDARD * Checks that RPC response type names are RPCNameResponse or ServiceNameRPCNameResponse (configurable). SERVICE_SUFFIX STANDARD * Checks that services have a consistent suffix (configurable, default suffix is "Service"). COMMENT_ENUM COMMENTS Checks that enums have non-empty comments. COMMENT_ENUM_VALUE COMMENTS Checks that enum values have non-empty comments. COMMENT_FIELD COMMENTS Checks that fields have non-empty comments. COMMENT_MESSAGE COMMENTS Checks that messages have non-empty comments. COMMENT_ONEOF COMMENTS Checks that oneofs have non-empty comments. COMMENT_RPC COMMENTS Checks that RPCs have non-empty comments. COMMENT_SERVICE COMMENTS Checks that services have non-empty comments. RPC_NO_CLIENT_STREAMING UNARY_RPC Checks that RPCs are not client streaming. RPC_NO_SERVER_STREAMING UNARY_RPC Checks that RPCs are not server streaming. PACKAGE_NO_IMPORT_CYCLE Checks that packages do not have import cycles. ` testRunStdout( t, nil, 0, expectedStdout, "mod", "ls-lint-rules", "--all", ) } func TestCheckPolicyConfigLsLintRulesConfigured(t *testing.T) { t.Parallel() expectedConfiguredOnlyStdout, err := os.ReadFile(filepath.Join("testdata", "policy_list_rules", "expected_ls_lint_rules_configured_only.txt")) require.NoError(t, err) testRunStdout( t, nil, 0, string(expectedConfiguredOnlyStdout), "config", "ls-lint-rules", "--config", filepath.Join("testdata", "policy_list_rules", "buf.yaml"), "--configured-only", ) } func TestCheckLsLintRulesFromConfig(t *testing.T) { t.Parallel() testRunStdout( t, nil, 0, ` ID CATEGORIES DEFAULT PURPOSE PACKAGE_DIRECTORY_MATCH MINIMAL, BASIC, STANDARD, FILE_LAYOUT * Checks that all files are in a directory that matches their package name. ENUM_NO_ALLOW_ALIAS MINIMAL, BASIC, STANDARD, SENSIBLE * Checks that enums do not have the allow_alias option set. `, "mod", "ls-lint-rules", "--config", filepath.Join("testdata", "small_list_rules", "buf.yaml"), ) // defaults only, built-ins and plugins. testLsRuleOutputJSON( t, check.RuleTypeLint, `{ "version":"v2", "plugins":[{"plugin": "buf-plugin-suffix"}] }`, append( xslices.Filter(builtinLintRulesV2, func(lintRule *outputCheckRule) bool { return lintRule.Default }), &outputCheckRule{ID: "RPC_BANNED_SUFFIXES", Categories: []string{"OPERATION_SUFFIXES"}, Default: true, Purpose: "Ensure that there are no RPCs with the list of configured banned suffixes.", Plugin: "buf-plugin-suffix"}, &outputCheckRule{ID: "SERVICE_BANNED_SUFFIXES", Categories: []string{"OPERATION_SUFFIXES"}, Default: true, Purpose: "Ensure that there are no services with the list of configured banned suffixes.", Plugin: "buf-plugin-suffix"}, ), ) // configure a deprecated category and a non-deprecated built-in category. // deprecated category contains some non-deprecated rules. testLsRuleOutputJSON( t, check.RuleTypeLint, `{ "version":"v2", "lint": { "use": ["MINIMAL", "RESOURCE_SUFFIXES"], }, "plugins":[{"plugin": "buf-plugin-suffix"}] }`, []*outputCheckRule{ {ID: "DIRECTORY_SAME_PACKAGE", Categories: []string{"MINIMAL", "BASIC", "STANDARD"}, Default: true, Purpose: "Checks that all files in a given directory are in the same package."}, {ID: "PACKAGE_DEFINED", Categories: []string{"MINIMAL", "BASIC", "STANDARD"}, Default: true, Purpose: "Checks that all files have a package defined."}, {ID: "PACKAGE_DIRECTORY_MATCH", Categories: []string{"MINIMAL", "BASIC", "STANDARD"}, Default: true, Purpose: "Checks that all files are in a directory that matches their package name."}, {ID: "PACKAGE_NO_IMPORT_CYCLE", Categories: []string{"MINIMAL", "BASIC", "STANDARD"}, Default: true, Purpose: "Checks that packages do not have import cycles."}, {ID: "PACKAGE_SAME_DIRECTORY", Categories: []string{"MINIMAL", "BASIC", "STANDARD"}, Default: true, Purpose: "Checks that all files with a given package are in the same directory."}, {ID: "ENUM_VALUE_BANNED_SUFFIXES", Categories: []string{"ATTRIBUTES_SUFFIXES"}, Default: false, Purpose: "Ensure that there are no enum values of top-level enums with the list of configured banned suffixes.", Plugin: "buf-plugin-suffix"}, {ID: "FIELD_BANNED_SUFFIXES", Categories: []string{"ATTRIBUTES_SUFFIXES"}, Default: false, Purpose: "Ensure that there are no fields with the list of configured banned suffixes.", Plugin: "buf-plugin-suffix"}, }, ) // configure a deprecated category and a non-deprecated category, no built-ins. testLsRuleOutputJSON( t, check.RuleTypeLint, `{ "version":"v2", "lint": { "use": ["OPERATION_SUFFIXES","ATTRIBUTES_SUFFIXES", "RESOURCE_SUFFIXES"], }, "plugins":[{"plugin": "buf-plugin-suffix"}] }`, []*outputCheckRule{ {ID: "RPC_BANNED_SUFFIXES", Categories: []string{"OPERATION_SUFFIXES"}, Default: true, Purpose: "Ensure that there are no RPCs with the list of configured banned suffixes.", Plugin: "buf-plugin-suffix"}, {ID: "SERVICE_BANNED_SUFFIXES", Categories: []string{"OPERATION_SUFFIXES"}, Default: true, Purpose: "Ensure that there are no services with the list of configured banned suffixes.", Plugin: "buf-plugin-suffix"}, {ID: "ENUM_VALUE_BANNED_SUFFIXES", Categories: []string{"ATTRIBUTES_SUFFIXES"}, Default: false, Purpose: "Ensure that there are no enum values of top-level enums with the list of configured banned suffixes.", Plugin: "buf-plugin-suffix"}, {ID: "FIELD_BANNED_SUFFIXES", Categories: []string{"ATTRIBUTES_SUFFIXES"}, Default: false, Purpose: "Ensure that there are no fields with the list of configured banned suffixes.", Plugin: "buf-plugin-suffix"}, }, ) // configure a deprecated category and a non-deprecated category, no built-ins. // note: ATTRIBUTES_SUFFIXES is not in USE, but is the replacement category for // RESOURCE_SUFFIXES. testLsRuleOutputJSON( t, check.RuleTypeLint, `{ "version":"v2", "lint": { "use": ["OPERATION_SUFFIXES", "RESOURCE_SUFFIXES"], }, "plugins":[{"plugin": "buf-plugin-suffix"}] }`, []*outputCheckRule{ {ID: "RPC_BANNED_SUFFIXES", Categories: []string{"OPERATION_SUFFIXES"}, Default: true, Purpose: "Ensure that there are no RPCs with the list of configured banned suffixes.", Plugin: "buf-plugin-suffix"}, {ID: "SERVICE_BANNED_SUFFIXES", Categories: []string{"OPERATION_SUFFIXES"}, Default: true, Purpose: "Ensure that there are no services with the list of configured banned suffixes.", Plugin: "buf-plugin-suffix"}, {ID: "ENUM_VALUE_BANNED_SUFFIXES", Categories: []string{"ATTRIBUTES_SUFFIXES"}, Default: false, Purpose: "Ensure that there are no enum values of top-level enums with the list of configured banned suffixes.", Plugin: "buf-plugin-suffix"}, {ID: "FIELD_BANNED_SUFFIXES", Categories: []string{"ATTRIBUTES_SUFFIXES"}, Default: false, Purpose: "Ensure that there are no fields with the list of configured banned suffixes.", Plugin: "buf-plugin-suffix"}, }, ) // configure a mix of rules from built-ins and plugins. MESSAGE_BANNED_SUFFIXES is a deprecated // rule, expect to print its replacement, FIELD_BANNED_SUFFIXES. testLsRuleOutputJSON( t, check.RuleTypeLint, `{ "version":"v2", "lint": { "use": ["RPC_BANNED_SUFFIXES","SERVICE_SUFFIX", "MESSAGE_BANNED_SUFFIXES"], }, "plugins":[{"plugin": "buf-plugin-suffix"}] }`, []*outputCheckRule{ {ID: "SERVICE_SUFFIX", Categories: []string{"STANDARD"}, Default: true, Purpose: "Checks that services have a consistent suffix (configurable, default suffix is \"Service\")."}, {ID: "RPC_BANNED_SUFFIXES", Categories: []string{"OPERATION_SUFFIXES"}, Default: true, Purpose: "Ensure that there are no RPCs with the list of configured banned suffixes.", Plugin: "buf-plugin-suffix"}, {ID: "FIELD_BANNED_SUFFIXES", Categories: []string{"ATTRIBUTES_SUFFIXES"}, Default: false, Purpose: "Ensure that there are no fields with the list of configured banned suffixes.", Plugin: "buf-plugin-suffix"}, }, ) // configure a mix of categories and rules from built-ins and plugins. testLsRuleOutputJSON( t, check.RuleTypeLint, `{ "version":"v2", "lint": { "use": ["RPC_BANNED_SUFFIXES","SERVICE_SUFFIX", "MINIMAL", "OPERATION_SUFFIXES"], }, "plugins":[{"plugin": "buf-plugin-suffix"}] }`, []*outputCheckRule{ {ID: "DIRECTORY_SAME_PACKAGE", Categories: []string{"MINIMAL", "BASIC", "STANDARD"}, Default: true, Purpose: "Checks that all files in a given directory are in the same package."}, {ID: "PACKAGE_DEFINED", Categories: []string{"MINIMAL", "BASIC", "STANDARD"}, Default: true, Purpose: "Checks that all files have a package defined."}, {ID: "PACKAGE_DIRECTORY_MATCH", Categories: []string{"MINIMAL", "BASIC", "STANDARD"}, Default: true, Purpose: "Checks that all files are in a directory that matches their package name."}, {ID: "PACKAGE_NO_IMPORT_CYCLE", Categories: []string{"MINIMAL", "BASIC", "STANDARD"}, Default: true, Purpose: "Checks that packages do not have import cycles."}, {ID: "PACKAGE_SAME_DIRECTORY", Categories: []string{"MINIMAL", "BASIC", "STANDARD"}, Default: true, Purpose: "Checks that all files with a given package are in the same directory."}, {ID: "SERVICE_SUFFIX", Categories: []string{"STANDARD"}, Default: true, Purpose: "Checks that services have a consistent suffix (configurable, default suffix is \"Service\")."}, {ID: "RPC_BANNED_SUFFIXES", Categories: []string{"OPERATION_SUFFIXES"}, Default: true, Purpose: "Ensure that there are no RPCs with the list of configured banned suffixes.", Plugin: "buf-plugin-suffix"}, {ID: "SERVICE_BANNED_SUFFIXES", Categories: []string{"OPERATION_SUFFIXES"}, Default: true, Purpose: "Ensure that there are no services with the list of configured banned suffixes.", Plugin: "buf-plugin-suffix"}, }, ) } func TestCheckLsLintRulesV1Beta1(t *testing.T) { t.Parallel() expectedStdout := ` ID CATEGORIES DEFAULT PURPOSE DIRECTORY_SAME_PACKAGE MINIMAL, BASIC, STANDARD, FILE_LAYOUT * Checks that all files in a given directory are in the same package. PACKAGE_DIRECTORY_MATCH MINIMAL, BASIC, STANDARD, FILE_LAYOUT * Checks that all files are in a directory that matches their package name. PACKAGE_SAME_DIRECTORY MINIMAL, BASIC, STANDARD, FILE_LAYOUT * Checks that all files with a given package are in the same directory. PACKAGE_SAME_CSHARP_NAMESPACE MINIMAL, BASIC, STANDARD, PACKAGE_AFFINITY * Checks that all files with a given package have the same value for the csharp_namespace option. PACKAGE_SAME_GO_PACKAGE MINIMAL, BASIC, STANDARD, PACKAGE_AFFINITY * Checks that all files with a given package have the same value for the go_package option. PACKAGE_SAME_JAVA_MULTIPLE_FILES MINIMAL, BASIC, STANDARD, PACKAGE_AFFINITY * Checks that all files with a given package have the same value for the java_multiple_files option. PACKAGE_SAME_JAVA_PACKAGE MINIMAL, BASIC, STANDARD, PACKAGE_AFFINITY * Checks that all files with a given package have the same value for the java_package option. PACKAGE_SAME_PHP_NAMESPACE MINIMAL, BASIC, STANDARD, PACKAGE_AFFINITY * Checks that all files with a given package have the same value for the php_namespace option. PACKAGE_SAME_RUBY_PACKAGE MINIMAL, BASIC, STANDARD, PACKAGE_AFFINITY * Checks that all files with a given package have the same value for the ruby_package option. PACKAGE_SAME_SWIFT_PREFIX MINIMAL, BASIC, STANDARD, PACKAGE_AFFINITY * Checks that all files with a given package have the same value for the swift_prefix option. ENUM_NO_ALLOW_ALIAS MINIMAL, BASIC, STANDARD, SENSIBLE * Checks that enums do not have the allow_alias option set. FIELD_NO_DESCRIPTOR MINIMAL, BASIC, STANDARD, SENSIBLE * Checks that field names are not any capitalization of "descriptor" with any number of prefix or suffix underscores. IMPORT_NO_PUBLIC MINIMAL, BASIC, STANDARD, SENSIBLE * Checks that imports are not public. PACKAGE_DEFINED MINIMAL, BASIC, STANDARD, SENSIBLE * Checks that all files have a package defined. ENUM_PASCAL_CASE BASIC, STANDARD, STYLE_BASIC, STYLE_STANDARD * Checks that enums are PascalCase. ENUM_VALUE_UPPER_SNAKE_CASE BASIC, STANDARD, STYLE_BASIC, STYLE_STANDARD * Checks that enum values are UPPER_SNAKE_CASE. FIELD_LOWER_SNAKE_CASE BASIC, STANDARD, STYLE_BASIC, STYLE_STANDARD * Checks that field names are lower_snake_case. MESSAGE_PASCAL_CASE BASIC, STANDARD, STYLE_BASIC, STYLE_STANDARD * Checks that messages are PascalCase. ONEOF_LOWER_SNAKE_CASE BASIC, STANDARD, STYLE_BASIC, STYLE_STANDARD * Checks that oneof names are lower_snake_case. PACKAGE_LOWER_SNAKE_CASE BASIC, STANDARD, STYLE_BASIC, STYLE_STANDARD * Checks that packages are lower_snake.case. RPC_PASCAL_CASE BASIC, STANDARD, STYLE_BASIC, STYLE_STANDARD * Checks that RPCs are PascalCase. SERVICE_PASCAL_CASE BASIC, STANDARD, STYLE_BASIC, STYLE_STANDARD * Checks that services are PascalCase. ENUM_VALUE_PREFIX STANDARD, STYLE_STANDARD * Checks that enum values are prefixed with ENUM_NAME_UPPER_SNAKE_CASE. ENUM_ZERO_VALUE_SUFFIX STANDARD, STYLE_STANDARD * Checks that enum zero values have a consistent suffix (configurable, default suffix is "_UNSPECIFIED"). FILE_LOWER_SNAKE_CASE STANDARD, STYLE_STANDARD * Checks that filenames are lower_snake_case. PACKAGE_VERSION_SUFFIX STANDARD, STYLE_STANDARD * Checks that the last component of all packages is a version of the form v\d+, v\d+test.*, v\d+(alpha|beta)\d+, or v\d+p\d+(alpha|beta)\d+, where numbers are >=1. RPC_REQUEST_RESPONSE_UNIQUE STANDARD, STYLE_STANDARD * Checks that RPC request and response types are only used in one RPC (configurable). RPC_REQUEST_STANDARD_NAME STANDARD, STYLE_STANDARD * Checks that RPC request type names are RPCNameRequest or ServiceNameRPCNameRequest (configurable). RPC_RESPONSE_STANDARD_NAME STANDARD, STYLE_STANDARD * Checks that RPC response type names are RPCNameResponse or ServiceNameRPCNameResponse (configurable). SERVICE_SUFFIX STANDARD, STYLE_STANDARD * Checks that services have a consistent suffix (configurable, default suffix is "Service"). COMMENT_ENUM COMMENTS Checks that enums have non-empty comments. COMMENT_ENUM_VALUE COMMENTS Checks that enum values have non-empty comments. COMMENT_FIELD COMMENTS Checks that fields have non-empty comments. COMMENT_MESSAGE COMMENTS Checks that messages have non-empty comments. COMMENT_ONEOF COMMENTS Checks that oneofs have non-empty comments. COMMENT_RPC COMMENTS Checks that RPCs have non-empty comments. COMMENT_SERVICE COMMENTS Checks that services have non-empty comments. RPC_NO_CLIENT_STREAMING UNARY_RPC Checks that RPCs are not client streaming. RPC_NO_SERVER_STREAMING UNARY_RPC Checks that RPCs are not server streaming. ENUM_FIRST_VALUE_ZERO OTHER Checks that all first values of enums have a numeric value of 0. ` testRunStdout( t, nil, 0, expectedStdout, "mod", "ls-lint-rules", "--version", "v1beta1", ) } func TestCheckLsLintRulesV2(t *testing.T) { t.Parallel() expectedStdout := ` ID CATEGORIES DEFAULT PURPOSE DIRECTORY_SAME_PACKAGE MINIMAL, BASIC, STANDARD * Checks that all files in a given directory are in the same package. PACKAGE_DEFINED MINIMAL, BASIC, STANDARD * Checks that all files have a package defined. PACKAGE_DIRECTORY_MATCH MINIMAL, BASIC, STANDARD * Checks that all files are in a directory that matches their package name. PACKAGE_NO_IMPORT_CYCLE MINIMAL, BASIC, STANDARD * Checks that packages do not have import cycles. PACKAGE_SAME_DIRECTORY MINIMAL, BASIC, STANDARD * Checks that all files with a given package are in the same directory. ENUM_FIRST_VALUE_ZERO BASIC, STANDARD * Checks that all first values of enums have a numeric value of 0. ENUM_NO_ALLOW_ALIAS BASIC, STANDARD * Checks that enums do not have the allow_alias option set. ENUM_PASCAL_CASE BASIC, STANDARD * Checks that enums are PascalCase. ENUM_VALUE_UPPER_SNAKE_CASE BASIC, STANDARD * Checks that enum values are UPPER_SNAKE_CASE. FIELD_LOWER_SNAKE_CASE BASIC, STANDARD * Checks that field names are lower_snake_case. FIELD_NOT_REQUIRED BASIC, STANDARD * Checks that fields are not configured to be required. IMPORT_NO_PUBLIC BASIC, STANDARD * Checks that imports are not public. IMPORT_USED BASIC, STANDARD * Checks that imports are used. MESSAGE_PASCAL_CASE BASIC, STANDARD * Checks that messages are PascalCase. ONEOF_LOWER_SNAKE_CASE BASIC, STANDARD * Checks that oneof names are lower_snake_case. PACKAGE_LOWER_SNAKE_CASE BASIC, STANDARD * Checks that packages are lower_snake.case. PACKAGE_SAME_CSHARP_NAMESPACE BASIC, STANDARD * Checks that all files with a given package have the same value for the csharp_namespace option. PACKAGE_SAME_GO_PACKAGE BASIC, STANDARD * Checks that all files with a given package have the same value for the go_package option. PACKAGE_SAME_JAVA_MULTIPLE_FILES BASIC, STANDARD * Checks that all files with a given package have the same value for the java_multiple_files option. PACKAGE_SAME_JAVA_PACKAGE BASIC, STANDARD * Checks that all files with a given package have the same value for the java_package option. PACKAGE_SAME_PHP_NAMESPACE BASIC, STANDARD * Checks that all files with a given package have the same value for the php_namespace option. PACKAGE_SAME_RUBY_PACKAGE BASIC, STANDARD * Checks that all files with a given package have the same value for the ruby_package option. PACKAGE_SAME_SWIFT_PREFIX BASIC, STANDARD * Checks that all files with a given package have the same value for the swift_prefix option. RPC_PASCAL_CASE BASIC, STANDARD * Checks that RPCs are PascalCase. SERVICE_PASCAL_CASE BASIC, STANDARD * Checks that services are PascalCase. SYNTAX_SPECIFIED BASIC, STANDARD * Checks that all files have a syntax specified. ENUM_VALUE_PREFIX STANDARD * Checks that enum values are prefixed with ENUM_NAME_UPPER_SNAKE_CASE. ENUM_ZERO_VALUE_SUFFIX STANDARD * Checks that enum zero values have a consistent suffix (configurable, default suffix is "_UNSPECIFIED"). FILE_LOWER_SNAKE_CASE STANDARD * Checks that filenames are lower_snake_case. PACKAGE_VERSION_SUFFIX STANDARD * Checks that the last component of all packages is a version of the form v\d+, v\d+test.*, v\d+(alpha|beta)\d+, or v\d+p\d+(alpha|beta)\d+, where numbers are >=1. PROTOVALIDATE STANDARD * Checks that protovalidate rules are valid and all CEL expressions compile. RPC_REQUEST_RESPONSE_UNIQUE STANDARD * Checks that RPC request and response types are only used in one RPC (configurable). RPC_REQUEST_STANDARD_NAME STANDARD * Checks that RPC request type names are RPCNameRequest or ServiceNameRPCNameRequest (configurable). RPC_RESPONSE_STANDARD_NAME STANDARD * Checks that RPC response type names are RPCNameResponse or ServiceNameRPCNameResponse (configurable). SERVICE_SUFFIX STANDARD * Checks that services have a consistent suffix (configurable, default suffix is "Service"). COMMENT_ENUM COMMENTS Checks that enums have non-empty comments. COMMENT_ENUM_VALUE COMMENTS Checks that enum values have non-empty comments. COMMENT_FIELD COMMENTS Checks that fields have non-empty comments. COMMENT_MESSAGE COMMENTS Checks that messages have non-empty comments. COMMENT_ONEOF COMMENTS Checks that oneofs have non-empty comments. COMMENT_RPC COMMENTS Checks that RPCs have non-empty comments. COMMENT_SERVICE COMMENTS Checks that services have non-empty comments. RPC_NO_CLIENT_STREAMING UNARY_RPC Checks that RPCs are not client streaming. RPC_NO_SERVER_STREAMING UNARY_RPC Checks that RPCs are not server streaming. STABLE_PACKAGE_NO_IMPORT_UNSTABLE Checks that all files that have stable versioned packages do not import packages with unstable version packages. ` testRunStdout( t, nil, 0, expectedStdout, "config", "ls-lint-rules", "--version", "v2", ) } func TestCheckLsBreakingRulesV1(t *testing.T) { t.Parallel() expectedStdout := ` ID CATEGORIES DEFAULT PURPOSE ENUM_NO_DELETE FILE * Checks that enums are not deleted from a given file. FILE_NO_DELETE FILE * Checks that files are not deleted. MESSAGE_NO_DELETE FILE * Checks that messages are not deleted from a given file. SERVICE_NO_DELETE FILE * Checks that services are not deleted from a given file. ENUM_SAME_TYPE FILE, PACKAGE * Checks that enums have the same type (open vs closed). ENUM_VALUE_NO_DELETE FILE, PACKAGE * Checks that enum values are not deleted from a given enum. EXTENSION_MESSAGE_NO_DELETE FILE, PACKAGE * Checks that extension ranges are not deleted from a given message. FIELD_NO_DELETE FILE, PACKAGE * Checks that fields are not deleted from a given message. FIELD_SAME_CARDINALITY FILE, PACKAGE * Checks that fields have the same cardinalities in a given message. FIELD_SAME_CPP_STRING_TYPE FILE, PACKAGE * Checks that fields have the same C++ string type, based on ctype field option or (pb.cpp).string_type feature. FIELD_SAME_JAVA_UTF8_VALIDATION FILE, PACKAGE * Checks that fields have the same Java string UTF8 validation, based on java_string_check_utf8 file option or (pb.java).utf8_validation feature. FIELD_SAME_JSTYPE FILE, PACKAGE * Checks that fields have the same value for the jstype option. FIELD_SAME_TYPE FILE, PACKAGE * Checks that fields have the same types in a given message. FIELD_SAME_UTF8_VALIDATION FILE, PACKAGE * Checks that string fields have the same UTF8 validation mode. FILE_SAME_CC_ENABLE_ARENAS FILE, PACKAGE * Checks that files have the same value for the cc_enable_arenas option. FILE_SAME_CC_GENERIC_SERVICES FILE, PACKAGE * Checks that files have the same value for the cc_generic_services option. FILE_SAME_CSHARP_NAMESPACE FILE, PACKAGE * Checks that files have the same value for the csharp_namespace option. FILE_SAME_GO_PACKAGE FILE, PACKAGE * Checks that files have the same value for the go_package option. FILE_SAME_JAVA_GENERIC_SERVICES FILE, PACKAGE * Checks that files have the same value for the java_generic_services option. FILE_SAME_JAVA_MULTIPLE_FILES FILE, PACKAGE * Checks that files have the same value for the java_multiple_files option. FILE_SAME_JAVA_OUTER_CLASSNAME FILE, PACKAGE * Checks that files have the same value for the java_outer_classname option. FILE_SAME_JAVA_PACKAGE FILE, PACKAGE * Checks that files have the same value for the java_package option. FILE_SAME_OBJC_CLASS_PREFIX FILE, PACKAGE * Checks that files have the same value for the objc_class_prefix option. FILE_SAME_OPTIMIZE_FOR FILE, PACKAGE * Checks that files have the same value for the optimize_for option. FILE_SAME_PHP_CLASS_PREFIX FILE, PACKAGE * Checks that files have the same value for the php_class_prefix option. FILE_SAME_PHP_METADATA_NAMESPACE FILE, PACKAGE * Checks that files have the same value for the php_metadata_namespace option. FILE_SAME_PHP_NAMESPACE FILE, PACKAGE * Checks that files have the same value for the php_namespace option. FILE_SAME_PY_GENERIC_SERVICES FILE, PACKAGE * Checks that files have the same value for the py_generic_services option. FILE_SAME_RUBY_PACKAGE FILE, PACKAGE * Checks that files have the same value for the ruby_package option. FILE_SAME_SWIFT_PREFIX FILE, PACKAGE * Checks that files have the same value for the swift_prefix option. FILE_SAME_SYNTAX FILE, PACKAGE * Checks that files have the same syntax. MESSAGE_NO_REMOVE_STANDARD_DESCRIPTOR_ACCESSOR FILE, PACKAGE * Checks that messages do not change the no_standard_descriptor_accessor option from false or unset to true. ONEOF_NO_DELETE FILE, PACKAGE * Checks that oneofs are not deleted from a given message. RPC_NO_DELETE FILE, PACKAGE * Checks that rpcs are not deleted from a given service. ENUM_SAME_JSON_FORMAT FILE, PACKAGE, WIRE_JSON * Checks that enums have the same JSON format support. ENUM_VALUE_SAME_NAME FILE, PACKAGE, WIRE_JSON * Checks that enum values have the same name. FIELD_SAME_JSON_NAME FILE, PACKAGE, WIRE_JSON * Checks that fields have the same value for the json_name option. FIELD_SAME_NAME FILE, PACKAGE, WIRE_JSON * Checks that fields have the same names in a given message. MESSAGE_SAME_JSON_FORMAT FILE, PACKAGE, WIRE_JSON * Checks that messages have the same JSON format support. FIELD_SAME_ONEOF FILE, PACKAGE, WIRE_JSON, WIRE * Checks that fields have the same oneofs in a given message. FILE_SAME_PACKAGE FILE, PACKAGE, WIRE_JSON, WIRE * Checks that files have the same package. MESSAGE_SAME_REQUIRED_FIELDS FILE, PACKAGE, WIRE_JSON, WIRE * Checks that messages have no added or deleted required fields. RESERVED_ENUM_NO_DELETE FILE, PACKAGE, WIRE_JSON, WIRE * Checks that reserved ranges and names are not deleted from a given enum. RESERVED_MESSAGE_NO_DELETE FILE, PACKAGE, WIRE_JSON, WIRE * Checks that reserved ranges and names are not deleted from a given message. RPC_SAME_CLIENT_STREAMING FILE, PACKAGE, WIRE_JSON, WIRE * Checks that rpcs have the same client streaming value. RPC_SAME_IDEMPOTENCY_LEVEL FILE, PACKAGE, WIRE_JSON, WIRE * Checks that rpcs have the same value for the idempotency_level option. RPC_SAME_REQUEST_TYPE FILE, PACKAGE, WIRE_JSON, WIRE * Checks that rpcs are have the same request type. RPC_SAME_RESPONSE_TYPE FILE, PACKAGE, WIRE_JSON, WIRE * Checks that rpcs are have the same response type. RPC_SAME_SERVER_STREAMING FILE, PACKAGE, WIRE_JSON, WIRE * Checks that rpcs have the same server streaming value. PACKAGE_ENUM_NO_DELETE PACKAGE Checks that enums are not deleted from a given package. PACKAGE_MESSAGE_NO_DELETE PACKAGE Checks that messages are not deleted from a given package. PACKAGE_NO_DELETE PACKAGE Checks that packages are not deleted. PACKAGE_SERVICE_NO_DELETE PACKAGE Checks that services are not deleted from a given package. ENUM_VALUE_NO_DELETE_UNLESS_NAME_RESERVED WIRE_JSON Checks that enum values are not deleted from a given enum unless the name is reserved. FIELD_NO_DELETE_UNLESS_NAME_RESERVED WIRE_JSON Checks that fields are not deleted from a given message unless the name is reserved. FIELD_WIRE_JSON_COMPATIBLE_CARDINALITY WIRE_JSON Checks that fields have wire and JSON compatible cardinalities in a given message. FIELD_WIRE_JSON_COMPATIBLE_TYPE WIRE_JSON Checks that fields have wire and JSON compatible types in a given message. ENUM_VALUE_NO_DELETE_UNLESS_NUMBER_RESERVED WIRE_JSON, WIRE Checks that enum values are not deleted from a given enum unless the number is reserved. FIELD_NO_DELETE_UNLESS_NUMBER_RESERVED WIRE_JSON, WIRE Checks that fields are not deleted from a given message unless the number is reserved. FIELD_WIRE_COMPATIBLE_CARDINALITY WIRE Checks that fields have wire-compatible cardinalities in a given message. FIELD_WIRE_COMPATIBLE_TYPE WIRE Checks that fields have wire-compatible types in a given message. ` testRunStdout( t, nil, 0, expectedStdout, "mod", "ls-breaking-rules", "--version", "v1", ) } func TestCheckLsBreakingRulesV1Beta1(t *testing.T) { t.Parallel() expectedStdout := ` ID CATEGORIES DEFAULT PURPOSE ENUM_NO_DELETE FILE * Checks that enums are not deleted from a given file. FILE_NO_DELETE FILE * Checks that files are not deleted. FILE_SAME_PACKAGE FILE * Checks that files have the same package. MESSAGE_NO_DELETE FILE * Checks that messages are not deleted from a given file. SERVICE_NO_DELETE FILE * Checks that services are not deleted from a given file. ENUM_SAME_TYPE FILE, PACKAGE * Checks that enums have the same type (open vs closed). ENUM_VALUE_NO_DELETE FILE, PACKAGE * Checks that enum values are not deleted from a given enum. EXTENSION_MESSAGE_NO_DELETE FILE, PACKAGE * Checks that extension ranges are not deleted from a given message. FIELD_NO_DELETE FILE, PACKAGE * Checks that fields are not deleted from a given message. FIELD_SAME_CPP_STRING_TYPE FILE, PACKAGE * Checks that fields have the same C++ string type, based on ctype field option or (pb.cpp).string_type feature. FIELD_SAME_JAVA_UTF8_VALIDATION FILE, PACKAGE * Checks that fields have the same Java string UTF8 validation, based on java_string_check_utf8 file option or (pb.java).utf8_validation feature. FIELD_SAME_JSTYPE FILE, PACKAGE * Checks that fields have the same value for the jstype option. FIELD_SAME_UTF8_VALIDATION FILE, PACKAGE * Checks that string fields have the same UTF8 validation mode. FILE_SAME_CC_ENABLE_ARENAS FILE, PACKAGE * Checks that files have the same value for the cc_enable_arenas option. FILE_SAME_CC_GENERIC_SERVICES FILE, PACKAGE * Checks that files have the same value for the cc_generic_services option. FILE_SAME_CSHARP_NAMESPACE FILE, PACKAGE * Checks that files have the same value for the csharp_namespace option. FILE_SAME_GO_PACKAGE FILE, PACKAGE * Checks that files have the same value for the go_package option. FILE_SAME_JAVA_GENERIC_SERVICES FILE, PACKAGE * Checks that files have the same value for the java_generic_services option. FILE_SAME_JAVA_MULTIPLE_FILES FILE, PACKAGE * Checks that files have the same value for the java_multiple_files option. FILE_SAME_JAVA_OUTER_CLASSNAME FILE, PACKAGE * Checks that files have the same value for the java_outer_classname option. FILE_SAME_JAVA_PACKAGE FILE, PACKAGE * Checks that files have the same value for the java_package option. FILE_SAME_OBJC_CLASS_PREFIX FILE, PACKAGE * Checks that files have the same value for the objc_class_prefix option. FILE_SAME_OPTIMIZE_FOR FILE, PACKAGE * Checks that files have the same value for the optimize_for option. FILE_SAME_PHP_CLASS_PREFIX FILE, PACKAGE * Checks that files have the same value for the php_class_prefix option. FILE_SAME_PHP_METADATA_NAMESPACE FILE, PACKAGE * Checks that files have the same value for the php_metadata_namespace option. FILE_SAME_PHP_NAMESPACE FILE, PACKAGE * Checks that files have the same value for the php_namespace option. FILE_SAME_PY_GENERIC_SERVICES FILE, PACKAGE * Checks that files have the same value for the py_generic_services option. FILE_SAME_RUBY_PACKAGE FILE, PACKAGE * Checks that files have the same value for the ruby_package option. FILE_SAME_SWIFT_PREFIX FILE, PACKAGE * Checks that files have the same value for the swift_prefix option. FILE_SAME_SYNTAX FILE, PACKAGE * Checks that files have the same syntax. MESSAGE_NO_REMOVE_STANDARD_DESCRIPTOR_ACCESSOR FILE, PACKAGE * Checks that messages do not change the no_standard_descriptor_accessor option from false or unset to true. ONEOF_NO_DELETE FILE, PACKAGE * Checks that oneofs are not deleted from a given message. RPC_NO_DELETE FILE, PACKAGE * Checks that rpcs are not deleted from a given service. ENUM_SAME_JSON_FORMAT FILE, PACKAGE, WIRE_JSON * Checks that enums have the same JSON format support. ENUM_VALUE_SAME_NAME FILE, PACKAGE, WIRE_JSON * Checks that enum values have the same name. FIELD_SAME_JSON_NAME FILE, PACKAGE, WIRE_JSON * Checks that fields have the same value for the json_name option. FIELD_SAME_NAME FILE, PACKAGE, WIRE_JSON * Checks that fields have the same names in a given message. MESSAGE_SAME_JSON_FORMAT FILE, PACKAGE, WIRE_JSON * Checks that messages have the same JSON format support. FIELD_SAME_CARDINALITY FILE, PACKAGE, WIRE_JSON, WIRE * Checks that fields have the same cardinalities in a given message. FIELD_SAME_ONEOF FILE, PACKAGE, WIRE_JSON, WIRE * Checks that fields have the same oneofs in a given message. FIELD_SAME_TYPE FILE, PACKAGE, WIRE_JSON, WIRE * Checks that fields have the same types in a given message. MESSAGE_SAME_REQUIRED_FIELDS FILE, PACKAGE, WIRE_JSON, WIRE * Checks that messages have no added or deleted required fields. RESERVED_ENUM_NO_DELETE FILE, PACKAGE, WIRE_JSON, WIRE * Checks that reserved ranges and names are not deleted from a given enum. RESERVED_MESSAGE_NO_DELETE FILE, PACKAGE, WIRE_JSON, WIRE * Checks that reserved ranges and names are not deleted from a given message. RPC_SAME_CLIENT_STREAMING FILE, PACKAGE, WIRE_JSON, WIRE * Checks that rpcs have the same client streaming value. RPC_SAME_IDEMPOTENCY_LEVEL FILE, PACKAGE, WIRE_JSON, WIRE * Checks that rpcs have the same value for the idempotency_level option. RPC_SAME_REQUEST_TYPE FILE, PACKAGE, WIRE_JSON, WIRE * Checks that rpcs are have the same request type. RPC_SAME_RESPONSE_TYPE FILE, PACKAGE, WIRE_JSON, WIRE * Checks that rpcs are have the same response type. RPC_SAME_SERVER_STREAMING FILE, PACKAGE, WIRE_JSON, WIRE * Checks that rpcs have the same server streaming value. PACKAGE_ENUM_NO_DELETE PACKAGE Checks that enums are not deleted from a given package. PACKAGE_MESSAGE_NO_DELETE PACKAGE Checks that messages are not deleted from a given package. PACKAGE_NO_DELETE PACKAGE Checks that packages are not deleted. PACKAGE_SERVICE_NO_DELETE PACKAGE Checks that services are not deleted from a given package. ENUM_VALUE_NO_DELETE_UNLESS_NAME_RESERVED WIRE_JSON Checks that enum values are not deleted from a given enum unless the name is reserved. FIELD_NO_DELETE_UNLESS_NAME_RESERVED WIRE_JSON Checks that fields are not deleted from a given message unless the name is reserved. FIELD_WIRE_JSON_COMPATIBLE_CARDINALITY WIRE_JSON Checks that fields have wire and JSON compatible cardinalities in a given message. ENUM_VALUE_NO_DELETE_UNLESS_NUMBER_RESERVED WIRE_JSON, WIRE Checks that enum values are not deleted from a given enum unless the number is reserved. FIELD_NO_DELETE_UNLESS_NUMBER_RESERVED WIRE_JSON, WIRE Checks that fields are not deleted from a given message unless the number is reserved. FIELD_WIRE_COMPATIBLE_CARDINALITY WIRE Checks that fields have wire-compatible cardinalities in a given message. ` testRunStdout( t, nil, 0, expectedStdout, "mod", "ls-breaking-rules", "--version", "v1beta1", ) } func TestCheckLsBreakingRulesV2(t *testing.T) { t.Parallel() expectedStdout := ` ID CATEGORIES DEFAULT PURPOSE EXTENSION_NO_DELETE FILE * Checks that extensions are not deleted from a given file. SERVICE_NO_DELETE FILE * Checks that services are not deleted from a given file. ENUM_NO_DELETE CSR, FILE * Checks that enums are not deleted from a given file. FILE_NO_DELETE CSR, FILE * Checks that files are not deleted. MESSAGE_NO_DELETE CSR, FILE * Checks that messages are not deleted from a given file. ENUM_SAME_TYPE FILE, PACKAGE * Checks that enums have the same type (open vs closed). FIELD_SAME_CARDINALITY FILE, PACKAGE * Checks that fields have the same cardinalities in a given message. FIELD_SAME_CPP_STRING_TYPE FILE, PACKAGE * Checks that fields have the same C++ string type, based on ctype field option or (pb.cpp).string_type feature. FIELD_SAME_JAVA_UTF8_VALIDATION FILE, PACKAGE * Checks that fields have the same Java string UTF8 validation, based on java_string_check_utf8 file option or (pb.java).utf8_validation feature. FIELD_SAME_JSTYPE FILE, PACKAGE * Checks that fields have the same value for the jstype option. FIELD_SAME_UTF8_VALIDATION FILE, PACKAGE * Checks that string fields have the same UTF8 validation mode. FILE_SAME_CC_ENABLE_ARENAS FILE, PACKAGE * Checks that files have the same value for the cc_enable_arenas option. FILE_SAME_CC_GENERIC_SERVICES FILE, PACKAGE * Checks that files have the same value for the cc_generic_services option. FILE_SAME_CSHARP_NAMESPACE FILE, PACKAGE * Checks that files have the same value for the csharp_namespace option. FILE_SAME_GO_PACKAGE FILE, PACKAGE * Checks that files have the same value for the go_package option. FILE_SAME_JAVA_GENERIC_SERVICES FILE, PACKAGE * Checks that files have the same value for the java_generic_services option. FILE_SAME_JAVA_MULTIPLE_FILES FILE, PACKAGE * Checks that files have the same value for the java_multiple_files option. FILE_SAME_JAVA_OUTER_CLASSNAME FILE, PACKAGE * Checks that files have the same value for the java_outer_classname option. FILE_SAME_JAVA_PACKAGE FILE, PACKAGE * Checks that files have the same value for the java_package option. FILE_SAME_OBJC_CLASS_PREFIX FILE, PACKAGE * Checks that files have the same value for the objc_class_prefix option. FILE_SAME_OPTIMIZE_FOR FILE, PACKAGE * Checks that files have the same value for the optimize_for option. FILE_SAME_PHP_CLASS_PREFIX FILE, PACKAGE * Checks that files have the same value for the php_class_prefix option. FILE_SAME_PHP_METADATA_NAMESPACE FILE, PACKAGE * Checks that files have the same value for the php_metadata_namespace option. FILE_SAME_PHP_NAMESPACE FILE, PACKAGE * Checks that files have the same value for the php_namespace option. FILE_SAME_PY_GENERIC_SERVICES FILE, PACKAGE * Checks that files have the same value for the py_generic_services option. FILE_SAME_RUBY_PACKAGE FILE, PACKAGE * Checks that files have the same value for the ruby_package option. FILE_SAME_SWIFT_PREFIX FILE, PACKAGE * Checks that files have the same value for the swift_prefix option. RPC_NO_DELETE FILE, PACKAGE * Checks that rpcs are not deleted from a given service. ENUM_VALUE_NO_DELETE CSR, FILE, PACKAGE * Checks that enum values are not deleted from a given enum. EXTENSION_MESSAGE_NO_DELETE CSR, FILE, PACKAGE * Checks that extension ranges are not deleted from a given message. FIELD_NO_DELETE CSR, FILE, PACKAGE * Checks that fields are not deleted from a given message. FIELD_SAME_TYPE CSR, FILE, PACKAGE * Checks that fields have the same types in a given message. FILE_SAME_SYNTAX CSR, FILE, PACKAGE * Checks that files have the same syntax. MESSAGE_NO_REMOVE_STANDARD_DESCRIPTOR_ACCESSOR CSR, FILE, PACKAGE * Checks that messages do not change the no_standard_descriptor_accessor option from false or unset to true. ONEOF_NO_DELETE CSR, FILE, PACKAGE * Checks that oneofs are not deleted from a given message. ENUM_SAME_JSON_FORMAT CSR, FILE, PACKAGE, WIRE_JSON * Checks that enums have the same JSON format support. ENUM_VALUE_SAME_NAME CSR, FILE, PACKAGE, WIRE_JSON * Checks that enum values have the same name. FIELD_SAME_JSON_NAME CSR, FILE, PACKAGE, WIRE_JSON * Checks that fields have the same value for the json_name option. FIELD_SAME_NAME CSR, FILE, PACKAGE, WIRE_JSON * Checks that fields have the same names in a given message. MESSAGE_SAME_JSON_FORMAT CSR, FILE, PACKAGE, WIRE_JSON * Checks that messages have the same JSON format support. FIELD_SAME_DEFAULT CSR, FILE, PACKAGE, WIRE_JSON, WIRE * Checks that fields have the same default value, if a default is specified. FIELD_SAME_ONEOF CSR, FILE, PACKAGE, WIRE_JSON, WIRE * Checks that fields have the same oneofs in a given message. FILE_SAME_PACKAGE CSR, FILE, PACKAGE, WIRE_JSON, WIRE * Checks that files have the same package. MESSAGE_SAME_REQUIRED_FIELDS CSR, FILE, PACKAGE, WIRE_JSON, WIRE * Checks that messages have no added or deleted required fields. RESERVED_ENUM_NO_DELETE CSR, FILE, PACKAGE, WIRE_JSON, WIRE * Checks that reserved ranges and names are not deleted from a given enum. RESERVED_MESSAGE_NO_DELETE CSR, FILE, PACKAGE, WIRE_JSON, WIRE * Checks that reserved ranges and names are not deleted from a given message. RPC_SAME_CLIENT_STREAMING CSR, FILE, PACKAGE, WIRE_JSON, WIRE * Checks that rpcs have the same client streaming value. RPC_SAME_IDEMPOTENCY_LEVEL CSR, FILE, PACKAGE, WIRE_JSON, WIRE * Checks that rpcs have the same value for the idempotency_level option. RPC_SAME_REQUEST_TYPE CSR, FILE, PACKAGE, WIRE_JSON, WIRE * Checks that rpcs are have the same request type. RPC_SAME_RESPONSE_TYPE CSR, FILE, PACKAGE, WIRE_JSON, WIRE * Checks that rpcs are have the same response type. RPC_SAME_SERVER_STREAMING CSR, FILE, PACKAGE, WIRE_JSON, WIRE * Checks that rpcs have the same server streaming value. PACKAGE_ENUM_NO_DELETE PACKAGE Checks that enums are not deleted from a given package. PACKAGE_EXTENSION_NO_DELETE PACKAGE Checks that extensions are not deleted from a given package. PACKAGE_MESSAGE_NO_DELETE PACKAGE Checks that messages are not deleted from a given package. PACKAGE_NO_DELETE PACKAGE Checks that packages are not deleted. PACKAGE_SERVICE_NO_DELETE PACKAGE Checks that services are not deleted from a given package. ENUM_VALUE_NO_DELETE_UNLESS_NAME_RESERVED CSR, WIRE_JSON Checks that enum values are not deleted from a given enum unless the name is reserved. FIELD_NO_DELETE_UNLESS_NAME_RESERVED CSR, WIRE_JSON Checks that fields are not deleted from a given message unless the name is reserved. FIELD_WIRE_JSON_COMPATIBLE_CARDINALITY CSR, WIRE_JSON Checks that fields have wire and JSON compatible cardinalities in a given message. FIELD_WIRE_JSON_COMPATIBLE_TYPE CSR, WIRE_JSON Checks that fields have wire and JSON compatible types in a given message. ENUM_VALUE_NO_DELETE_UNLESS_NUMBER_RESERVED CSR, WIRE_JSON, WIRE Checks that enum values are not deleted from a given enum unless the number is reserved. FIELD_NO_DELETE_UNLESS_NUMBER_RESERVED CSR, WIRE_JSON, WIRE Checks that fields are not deleted from a given message unless the number is reserved. FIELD_WIRE_COMPATIBLE_CARDINALITY WIRE Checks that fields have wire-compatible cardinalities in a given message. FIELD_WIRE_COMPATIBLE_TYPE WIRE Checks that fields have wire-compatible types in a given message. ` testRunStdout( t, nil, 0, expectedStdout, "config", "ls-breaking-rules", "--version", "v2", ) } func TestCheckLsBreakingRulesFromConfig(t *testing.T) { t.Parallel() testRunStdout( t, nil, 0, ` ID CATEGORIES DEFAULT PURPOSE ENUM_VALUE_NO_DELETE FILE, PACKAGE * Checks that enum values are not deleted from a given enum. FIELD_SAME_JSTYPE FILE, PACKAGE * Checks that fields have the same value for the jstype option. `, "mod", "ls-breaking-rules", "--config", filepath.Join("testdata", "small_list_rules", "buf.yaml"), ) // defaults only, built-ins and plugins. testLsRuleOutputJSON( t, check.RuleTypeBreaking, `{ "version":"v2", "plugins":[{"plugin": "buf-plugin-suffix"}] }`, append( xslices.Filter(builtinBreakingRulesV2, func(breakingRule *outputCheckRule) bool { return breakingRule.Default }), &outputCheckRule{ID: "SERVICE_SUFFIXES_NO_CHANGE", Categories: []string{"OPERATION_SUFFIXES"}, Default: true, Purpose: "Ensure that services with configured suffixes are not deleted and do not have new RPCs or delete RPCs.", Plugin: "buf-plugin-suffix"}, ), ) // configure a deprecated category and a non-deprecated built-in category. // deprecated category contains some non-deprecated rules. testLsRuleOutputJSON( t, check.RuleTypeBreaking, `{ "version":"v2", "breaking": { "use": ["WIRE", "RESOURCE_SUFFIXES"], }, "plugins":[{"plugin": "buf-plugin-suffix"}] }`, append( xslices.Filter(builtinBreakingRulesV2, func(breakingRule *outputCheckRule) bool { return slices.Contains(breakingRule.Categories, "WIRE") }), &outputCheckRule{ID: "ENUM_SUFFIXES_NO_CHANGE", Categories: []string{"ATTRIBUTES_SUFFIXES"}, Default: false, Purpose: "Ensure that enums with configured suffixes are not deleted and do not have new enum values or delete enum values.", Plugin: "buf-plugin-suffix"}, &outputCheckRule{ID: "MESSAGE_SUFFIXES_NO_CHANGE", Categories: []string{"ATTRIBUTES_SUFFIXES"}, Default: false, Purpose: "Ensure that messages with configured suffixes are not deleted and do not have new fields or delete fields.", Plugin: "buf-plugin-suffix"}, ), ) // configure a deprecated category and a non-deprecated category, no built-ins. testLsRuleOutputJSON( t, check.RuleTypeBreaking, `{ "version":"v2", "breaking": { "use": ["OPERATION_SUFFIXES","ATTRIBUTES_SUFFIXES", "RESOURCE_SUFFIXES"], }, "plugins":[{"plugin": "buf-plugin-suffix"}] }`, []*outputCheckRule{ {ID: "SERVICE_SUFFIXES_NO_CHANGE", Categories: []string{"OPERATION_SUFFIXES"}, Default: true, Purpose: "Ensure that services with configured suffixes are not deleted and do not have new RPCs or delete RPCs.", Plugin: "buf-plugin-suffix"}, {ID: "ENUM_SUFFIXES_NO_CHANGE", Categories: []string{"ATTRIBUTES_SUFFIXES"}, Default: false, Purpose: "Ensure that enums with configured suffixes are not deleted and do not have new enum values or delete enum values.", Plugin: "buf-plugin-suffix"}, {ID: "MESSAGE_SUFFIXES_NO_CHANGE", Categories: []string{"ATTRIBUTES_SUFFIXES"}, Default: false, Purpose: "Ensure that messages with configured suffixes are not deleted and do not have new fields or delete fields.", Plugin: "buf-plugin-suffix"}, }, ) // configure a mix of rules from built-ins and plugins. testLsRuleOutputJSON( t, check.RuleTypeBreaking, `{ "version":"v2", "breaking": { "use": ["FIELD_WIRE_COMPATIBLE_TYPE", "PACKAGE", "ENUM_SUFFIXES_NO_CHANGE"], }, "plugins":[{"plugin": "buf-plugin-suffix"}] }`, append( xslices.Filter(builtinBreakingRulesV2, func(breakingRule *outputCheckRule) bool { return slices.Contains(breakingRule.Categories, "PACKAGE") }), &outputCheckRule{ID: "FIELD_WIRE_COMPATIBLE_TYPE", Categories: []string{"WIRE"}, Default: false, Purpose: "Checks that fields have wire-compatible types in a given message."}, &outputCheckRule{ID: "ENUM_SUFFIXES_NO_CHANGE", Categories: []string{"ATTRIBUTES_SUFFIXES"}, Default: false, Purpose: "Ensure that enums with configured suffixes are not deleted and do not have new enum values or delete enum values.", Plugin: "buf-plugin-suffix"}, ), ) // configure a mix of categories and rules from built-ins and plugins. testLsRuleOutputJSON( t, check.RuleTypeBreaking, `{ "version":"v2", "breaking": { "use": ["FIELD_WIRE_COMPATIBLE_TYPE", "PACKAGE", "ENUM_SUFFIXES_NO_CHANGE", "OPERATION_SUFFIXES"], }, "plugins":[{"plugin": "buf-plugin-suffix"}] }`, append( xslices.Filter(builtinBreakingRulesV2, func(breakingRule *outputCheckRule) bool { return slices.Contains(breakingRule.Categories, "PACKAGE") || breakingRule.ID == "FIELD_WIRE_COMPATIBLE_TYPE" }), &outputCheckRule{ID: "SERVICE_SUFFIXES_NO_CHANGE", Categories: []string{"OPERATION_SUFFIXES"}, Default: true, Purpose: "Ensure that services with configured suffixes are not deleted and do not have new RPCs or delete RPCs.", Plugin: "buf-plugin-suffix"}, &outputCheckRule{ID: "ENUM_SUFFIXES_NO_CHANGE", Categories: []string{"ATTRIBUTES_SUFFIXES"}, Default: false, Purpose: "Ensure that enums with configured suffixes are not deleted and do not have new enum values or delete enum values.", Plugin: "buf-plugin-suffix"}, ), ) } func TestCheckLsBreakingRulesFromConfigNotNamedBufYAML(t *testing.T) { t.Parallel() testRunStdout( t, nil, 0, ` ID CATEGORIES DEFAULT PURPOSE ENUM_VALUE_NO_DELETE FILE, PACKAGE * Checks that enum values are not deleted from a given enum. FIELD_SAME_JSTYPE FILE, PACKAGE * Checks that fields have the same value for the jstype option. `, "mod", "ls-breaking-rules", "--config", // making sure that .yml works filepath.Join("testdata", "small_list_rules_yml", "config.yml"), ) } func TestCheckLsBreakingRulesFromConfigExceptDeprecated(t *testing.T) { t.Parallel() for _, version := range bufconfig.AllFileVersions { t.Run(version.String(), func(t *testing.T) { t.Parallel() // Do not need any custom lint/breaking plugins here. client, err := bufcheck.NewClient(slogtestext.NewLogger(t)) require.NoError(t, err) allRules, err := client.AllRules(t.Context(), check.RuleTypeBreaking, version) require.NoError(t, err) allPackageIDs := make([]string, 0, len(allRules)) for _, rule := range allRules { if rule.Deprecated() { // Deprecated rules should not be associated with a category. // Instead, their replacements are associated with categories. assert.Empty(t, rule.Categories()) continue } var found bool for _, category := range rule.Categories() { if category.ID() == "PACKAGE" { found = true break } } if found { allPackageIDs = append(allPackageIDs, rule.ID()) } } sort.Strings(allPackageIDs) deprecations, err := bufcheck.GetDeprecatedIDToReplacementIDs(allRules) require.NoError(t, err) for deprecatedRule := range deprecations { t.Run(deprecatedRule, func(t *testing.T) { t.Parallel() ids := getRuleIDsFromLsBreaking(t, version.String(), []string{"PACKAGE"}, []string{deprecatedRule}) expectedIDs := make([]string, 0, len(allPackageIDs)) replacements := deprecations[deprecatedRule] for _, id := range allPackageIDs { if slices.Contains(replacements, id) { continue } expectedIDs = append(expectedIDs, id) } require.Equal(t, expectedIDs, ids) }) } }) } } func TestLsModulesWorkspaceV1(t *testing.T) { // Cannot be parallel since we chdir. pwd, err := osext.Getwd() require.NoError(t, err) defer func() { r := recover() assert.NoError(t, osext.Chdir(pwd)) if r != nil { panic(r) } }() require.NoError(t, osext.Chdir(filepath.Join(pwd, "testdata", "lsmodules", "workspacev1"))) testRunStdout( t, nil, 0, // default format is path ` a_v1 b_no_name_v1 c_v1beta1 d_no_file e_no_file f_no_name_v1beta1 `, "config", "ls-modules", ) testRunStdout( t, nil, 0, // format as path ` a_v1 b_no_name_v1 c_v1beta1 d_no_file e_no_file f_no_name_v1beta1 `, "config", "ls-modules", "--format", "path", ) testRunStdout( t, nil, 0, // format as name ` buf.build/bar/baz buf.build/foo/bar `, "config", "ls-modules", "--format", "name", ) testRunStdout( t, nil, 0, // format as json, sort by path ` {"path":"a_v1","name":"buf.build/foo/bar"} {"path":"b_no_name_v1"} {"path":"c_v1beta1","name":"buf.build/bar/baz"} {"path":"d_no_file"} {"path":"e_no_file"} {"path":"f_no_name_v1beta1"} `, "config", "ls-modules", "--format", "json", ) } func TestLsModulesWorkspaceV2(t *testing.T) { // Cannot be parallel since we chdir. pwd, err := osext.Getwd() require.NoError(t, err) defer func() { r := recover() assert.NoError(t, osext.Chdir(pwd)) if r != nil { panic(r) } }() require.NoError(t, osext.Chdir(filepath.Join(pwd, "testdata", "lsmodules", "workspacev2"))) testRunStdout( t, nil, 0, // default format is path ` a b_no_name c d_no_name `, "config", "ls-modules", ) testRunStdout( t, nil, 0, // format as path ` a b_no_name c d_no_name `, "config", "ls-modules", "--format", "path", ) testRunStdout( t, nil, 0, // format as name ` buf.build/bar/baz buf.build/foo/bar `, "config", "ls-modules", "--format", "name", ) testRunStdout( t, nil, 0, // format as json, sort by path ` {"path":"a","name":"buf.build/foo/bar"} {"path":"b_no_name"} {"path":"c","name":"buf.build/bar/baz"} {"path":"d_no_name"} `, "config", "ls-modules", "--format", "json", ) } func TestLsModulesModuleV1(t *testing.T) { // Cannot be parallel since we chdir. pwd, err := osext.Getwd() require.NoError(t, err) defer func() { r := recover() assert.NoError(t, osext.Chdir(pwd)) if r != nil { panic(r) } }() // with name require.NoError(t, osext.Chdir(filepath.Join(pwd, "testdata", "lsmodules", "workspacev1", "a_v1"))) testRunStdout( t, nil, 0, // default format is path ".", "config", "ls-modules", ) testRunStdout( t, nil, 0, ".", "config", "ls-modules", "--format", "path", ) testRunStdout( t, nil, 0, ` buf.build/foo/bar `, "config", "ls-modules", "--format", "name", ) testRunStdout( t, nil, 0, `{"path":".","name":"buf.build/foo/bar"}`, "config", "ls-modules", "--format", "json", ) // without name require.NoError(t, osext.Chdir(filepath.Join(pwd, "testdata", "lsmodules", "workspacev1", "b_no_name_v1"))) testRunStdout( t, nil, 0, // default format is path ".", "config", "ls-modules", ) testRunStdout( t, nil, 0, ".", "config", "ls-modules", "--format", "path", ) testRunStdout( t, nil, 0, "", // empty output "config", "ls-modules", "--format", "name", ) testRunStdout( t, nil, 0, `{"path":"."}`, "config", "ls-modules", "--format", "json", ) } func TestLsModulesModuleV1Beta1(t *testing.T) { // Cannot be parallel since we chdir. pwd, err := osext.Getwd() require.NoError(t, err) defer func() { r := recover() assert.NoError(t, osext.Chdir(pwd)) if r != nil { panic(r) } }() // with name require.NoError(t, osext.Chdir(filepath.Join(pwd, "testdata", "lsmodules", "workspacev1", "c_v1beta1"))) testRunStdout( t, nil, 0, // default format is path ".", "config", "ls-modules", ) testRunStdout( t, nil, 0, ".", "config", "ls-modules", "--format", "path", ) testRunStdout( t, nil, 0, "buf.build/bar/baz", "config", "ls-modules", "--format", "name", ) testRunStdout( t, nil, 0, `{"path":".","name":"buf.build/bar/baz"}`, "config", "ls-modules", "--format", "json", ) // without name require.NoError(t, osext.Chdir(filepath.Join(pwd, "testdata", "lsmodules", "workspacev1", "f_no_name_v1beta1"))) testRunStdout( t, nil, 0, // default format is path ".", "config", "ls-modules", ) testRunStdout( t, nil, 0, ".", "config", "ls-modules", "--format", "path", ) testRunStdout( t, nil, 0, "", // empty output "config", "ls-modules", "--format", "name", ) testRunStdout( t, nil, 0, `{"path":"."}`, "config", "ls-modules", "--format", "json", ) } func TestLsModulesNoConfig(t *testing.T) { // Cannot be parallel since we chdir. pwd, err := osext.Getwd() require.NoError(t, err) defer func() { r := recover() assert.NoError(t, osext.Chdir(pwd)) if r != nil { panic(r) } }() // with name require.NoError(t, osext.Chdir(t.TempDir())) testRunStdout( t, nil, 0, // default format is path ".", "config", "ls-modules", ) testRunStdout( t, nil, 0, ".", "config", "ls-modules", "--format", "path", ) testRunStdout( t, nil, 0, "", "config", "ls-modules", "--format", "name", ) testRunStdout( t, nil, 0, `{"path":"."}`, "config", "ls-modules", "--format", "json", ) } func TestLsModulesBothConfig(t *testing.T) { // Cannot be parallel since we chdir. pwd, err := osext.Getwd() require.NoError(t, err) defer func() { r := recover() assert.NoError(t, osext.Chdir(pwd)) if r != nil { panic(r) } }() require.NoError(t, osext.Chdir(filepath.Join(pwd, "testdata", "lsmodules", "extraconfigv1"))) testRunStderrContainsNoWarn( t, nil, 1, []string{"buf.yaml", "buf.work.yaml"}, "config", "ls-modules", ) require.NoError(t, osext.Chdir(filepath.Join(pwd, "testdata", "lsmodules", "extraconfigv2"))) testRunStderrContainsNoWarn( t, nil, 1, []string{"buf.yaml", "buf.work.yaml"}, "config", "ls-modules", ) } func TestLsModulesInvalidVersion(t *testing.T) { // Cannot be parallel since we chdir. pwd, err := osext.Getwd() require.NoError(t, err) defer func() { r := recover() assert.NoError(t, osext.Chdir(pwd)) if r != nil { panic(r) } }() require.NoError(t, osext.Chdir(filepath.Join(pwd, "testdata", "lsmodules", "workspaceinvalid"))) testRunStderr( t, nil, 1, `Failure: buf.work.yaml pointed to directory "proto" which has a v2 buf.yaml file`, "config", "ls-modules", ) } func TestLsModulesConfigFlag(t *testing.T) { t.Parallel() // v1beta1 testRunStdout( t, nil, 0, `{"path":".","name":"buf.build/bar/baz"}`, "config", "ls-modules", "--config", filepath.Join("testdata", "lsmodules", "workspacev1", "c_v1beta1", "buf.yaml"), "--format", "json", ) // v1 testRunStdout( t, nil, 0, `{"path":".","name":"buf.build/foo/bar"}`, "config", "ls-modules", "--config", filepath.Join("testdata", "lsmodules", "workspacev1", "a_v1", "buf.yaml"), "--format", "json", ) // v2 testRunStdout( t, nil, 0, ` {"path":"a","name":"buf.build/foo/bar"} {"path":"b_no_name"} {"path":"c","name":"buf.build/bar/baz"} {"path":"d_no_name"} `, "config", "ls-modules", "--config", filepath.Join("testdata", "lsmodules", "workspacev2", "buf.yaml"), "--format", "json", ) } func TestLsModulesConfigFlagTakesPrecedence(t *testing.T) { // Cannot be parallel since we chdir. pwd, err := osext.Getwd() require.NoError(t, err) defer func() { r := recover() assert.NoError(t, osext.Chdir(pwd)) if r != nil { panic(r) } }() require.NoError(t, osext.Chdir(filepath.Join(pwd, "testdata", "lsmodules", "workspacev1"))) testRunStdout( t, nil, 0, // default format is path ` a b_no_name c d_no_name `, "config", "ls-modules", "--config", filepath.Join(pwd, "testdata", "lsmodules", "workspacev2", "buf.yaml"), ) } func TestLsModulesWorkspaceV2DuplicateDirPath(t *testing.T) { // Cannot be parallel since we chdir. pwd, err := osext.Getwd() require.NoError(t, err) defer func() { r := recover() assert.NoError(t, osext.Chdir(pwd)) if r != nil { panic(r) } }() require.NoError(t, osext.Chdir(filepath.Join(pwd, "testdata", "workspace", "success", "duplicate_dir_path"))) testRunStdout( t, nil, 0, // default format is path ` proto/shared proto/shared proto/shared1 proto/shared1 separate `, "config", "ls-modules", ) testRunStdout( t, nil, 0, // format as path ` proto/shared proto/shared proto/shared1 proto/shared1 separate `, "config", "ls-modules", "--format", "path", ) testRunStdout( t, nil, 0, // format as name ` buf.build/shared/one buf.build/shared/zero `, "config", "ls-modules", "--format", "name", ) testRunStdout( t, nil, 0, // format as json, sort by path ` {"path":"proto/shared","excludes":["proto/shared/prefix/foo"]} {"path":"proto/shared","excludes":["proto/shared/prefix/bar"],"name":"buf.build/shared/zero"} {"path":"proto/shared1","includes":["proto/shared1/prefix/x"],"name":"buf.build/shared/one"} {"path":"proto/shared1","excludes":["proto/shared1/prefix/x"]} {"path":"separate"} `, "config", "ls-modules", "--format", "json", ) } func TestLsBreakingRulesDeprecated(t *testing.T) { t.Parallel() stdout := bytes.NewBuffer(nil) testRun(t, 0, nil, stdout, "mod", "ls-breaking-rules", "--version", "v1beta1") assert.NotContains(t, stdout.String(), "FIELD_SAME_CTYPE") assert.NotContains(t, stdout.String(), "FIELD_SAME_LABEL") assert.NotContains(t, stdout.String(), "FILE_SAME_JAVA_STRING_CHECK_UTF8") assert.NotContains(t, stdout.String(), "FILE_SAME_PHP_GENERIC_SERVICES") stdout = bytes.NewBuffer(nil) testRun(t, 0, nil, stdout, "mod", "ls-breaking-rules", "--version", "v1beta1", "--include-deprecated") assert.Contains(t, stdout.String(), "FIELD_SAME_CTYPE") assert.Contains(t, stdout.String(), "FIELD_SAME_LABEL") assert.Contains(t, stdout.String(), "FILE_SAME_JAVA_STRING_CHECK_UTF8") assert.Contains(t, stdout.String(), "FILE_SAME_PHP_GENERIC_SERVICES") stdout = bytes.NewBuffer(nil) testRun(t, 0, nil, stdout, "mod", "ls-breaking-rules", "--version", "v1") assert.NotContains(t, stdout.String(), "FIELD_SAME_CTYPE") assert.NotContains(t, stdout.String(), "FIELD_SAME_LABEL") assert.NotContains(t, stdout.String(), "FILE_SAME_JAVA_STRING_CHECK_UTF8") assert.NotContains(t, stdout.String(), "FILE_SAME_PHP_GENERIC_SERVICES") stdout = bytes.NewBuffer(nil) testRun(t, 0, nil, stdout, "mod", "ls-breaking-rules", "--version", "v1", "--include-deprecated") assert.Contains(t, stdout.String(), "FIELD_SAME_CTYPE") assert.Contains(t, stdout.String(), "FIELD_SAME_LABEL") assert.Contains(t, stdout.String(), "FILE_SAME_JAVA_STRING_CHECK_UTF8") assert.Contains(t, stdout.String(), "FILE_SAME_PHP_GENERIC_SERVICES") stdout = bytes.NewBuffer(nil) testRun(t, 0, nil, stdout, "config", "ls-breaking-rules", "--version", "v1beta1") assert.NotContains(t, stdout.String(), "FIELD_SAME_CTYPE") assert.NotContains(t, stdout.String(), "FIELD_SAME_LABEL") assert.NotContains(t, stdout.String(), "FILE_SAME_JAVA_STRING_CHECK_UTF8") assert.NotContains(t, stdout.String(), "FILE_SAME_PHP_GENERIC_SERVICES") stdout = bytes.NewBuffer(nil) testRun(t, 0, nil, stdout, "config", "ls-breaking-rules", "--version", "v1beta1", "--include-deprecated") assert.Contains(t, stdout.String(), "FIELD_SAME_CTYPE") assert.Contains(t, stdout.String(), "FIELD_SAME_LABEL") assert.Contains(t, stdout.String(), "FILE_SAME_JAVA_STRING_CHECK_UTF8") assert.Contains(t, stdout.String(), "FILE_SAME_PHP_GENERIC_SERVICES") stdout = bytes.NewBuffer(nil) testRun(t, 0, nil, stdout, "config", "ls-breaking-rules", "--version", "v1") assert.NotContains(t, stdout.String(), "FIELD_SAME_CTYPE") assert.NotContains(t, stdout.String(), "FIELD_SAME_LABEL") assert.NotContains(t, stdout.String(), "FILE_SAME_JAVA_STRING_CHECK_UTF8") assert.NotContains(t, stdout.String(), "FILE_SAME_PHP_GENERIC_SERVICES") stdout = bytes.NewBuffer(nil) testRun(t, 0, nil, stdout, "config", "ls-breaking-rules", "--version", "v1", "--include-deprecated") assert.Contains(t, stdout.String(), "FIELD_SAME_CTYPE") assert.Contains(t, stdout.String(), "FIELD_SAME_LABEL") assert.Contains(t, stdout.String(), "FILE_SAME_JAVA_STRING_CHECK_UTF8") assert.Contains(t, stdout.String(), "FILE_SAME_PHP_GENERIC_SERVICES") stdout = bytes.NewBuffer(nil) testRun(t, 0, nil, stdout, "config", "ls-breaking-rules", "--version", "v2") assert.NotContains(t, stdout.String(), "FIELD_SAME_CTYPE") assert.NotContains(t, stdout.String(), "FIELD_SAME_LABEL") assert.NotContains(t, stdout.String(), "FILE_SAME_JAVA_STRING_CHECK_UTF8") assert.NotContains(t, stdout.String(), "FILE_SAME_PHP_GENERIC_SERVICES") // The deprecated rules are omitted from v2. stdout = bytes.NewBuffer(nil) testRun(t, 0, nil, stdout, "config", "ls-breaking-rules", "--version", "v2", "--include-deprecated") assert.NotContains(t, stdout.String(), "FIELD_SAME_CTYPE") assert.NotContains(t, stdout.String(), "FIELD_SAME_LABEL") assert.NotContains(t, stdout.String(), "FILE_SAME_JAVA_STRING_CHECK_UTF8") assert.NotContains(t, stdout.String(), "FILE_SAME_PHP_GENERIC_SERVICES") // Test the non-all version too. Should never have deprecated rules. stdout = bytes.NewBuffer(nil) testRun(t, 0, nil, stdout, "mod", "ls-breaking-rules") assert.NotContains(t, stdout.String(), "FIELD_SAME_CTYPE") assert.NotContains(t, stdout.String(), "FIELD_SAME_LABEL") assert.NotContains(t, stdout.String(), "FILE_SAME_JAVA_STRING_CHECK_UTF8") assert.NotContains(t, stdout.String(), "FILE_SAME_PHP_GENERIC_SERVICES") stdout = bytes.NewBuffer(nil) testRun(t, 0, nil, stdout, "mod", "ls-breaking-rules", "--include-deprecated") assert.NotContains(t, stdout.String(), "FIELD_SAME_CTYPE") assert.NotContains(t, stdout.String(), "FIELD_SAME_LABEL") assert.NotContains(t, stdout.String(), "FILE_SAME_JAVA_STRING_CHECK_UTF8") assert.NotContains(t, stdout.String(), "FILE_SAME_PHP_GENERIC_SERVICES") stdout = bytes.NewBuffer(nil) testRun(t, 0, nil, stdout, "config", "ls-breaking-rules", "--configured-only") assert.NotContains(t, stdout.String(), "FIELD_SAME_CTYPE") assert.NotContains(t, stdout.String(), "FIELD_SAME_LABEL") assert.NotContains(t, stdout.String(), "FILE_SAME_JAVA_STRING_CHECK_UTF8") assert.NotContains(t, stdout.String(), "FILE_SAME_PHP_GENERIC_SERVICES") stdout = bytes.NewBuffer(nil) testRun(t, 0, nil, stdout, "config", "ls-breaking-rules", "--configured-only", "--include-deprecated") assert.NotContains(t, stdout.String(), "FIELD_SAME_CTYPE") assert.NotContains(t, stdout.String(), "FIELD_SAME_LABEL") assert.NotContains(t, stdout.String(), "FILE_SAME_JAVA_STRING_CHECK_UTF8") assert.NotContains(t, stdout.String(), "FILE_SAME_PHP_GENERIC_SERVICES") } func TestLsFiles(t *testing.T) { t.Parallel() testRunStdout( t, nil, 0, filepath.FromSlash(`testdata/success/buf/buf.proto`), "ls-files", filepath.Join("testdata", "success"), ) testRunStdout( t, nil, 0, filepath.FromSlash(`testdata/success/buf/buf.proto`), "ls-files", filepath.Join("testdata", "success", "buf", "buf.proto"), ) testRunStdout( t, nil, 0, filepath.FromSlash(`testdata/protofileref/success/buf.proto`), "ls-files", // test single file ref that is not part of the module or workspace filepath.Join("testdata", "protofileref", "success", "buf.proto"), ) testRunStdout( t, nil, 0, filepath.FromSlash(` testdata/protofileref/success/buf.proto testdata/protofileref/success/other.proto `), "ls-files", // test single file ref that is not part of the module or workspace fmt.Sprintf("%s#include_package_files=true", filepath.Join("testdata", "protofileref", "success", "buf.proto")), ) } func TestLsFilesIncludeImports(t *testing.T) { t.Parallel() testRunStdout( t, nil, 0, `google/protobuf/descriptor.proto `+filepath.FromSlash(`testdata/success/buf/buf.proto`), "ls-files", "--include-imports", filepath.Join("testdata", "success"), ) testRunStdout( t, nil, 0, `google/protobuf/descriptor.proto `+filepath.FromSlash(`testdata/protofileref/success/buf.proto`), "ls-files", "--include-imports", filepath.Join("testdata", "protofileref", "success", "buf.proto"), ) testRunStdout( t, nil, 0, `google/protobuf/descriptor.proto`+filepath.FromSlash(` testdata/protofileref/success/buf.proto testdata/protofileref/success/other.proto `), "ls-files", "--include-imports", fmt.Sprintf("%s#include_package_files=true", filepath.Join("testdata", "protofileref", "success", "buf.proto")), ) } func TestLsFilesIncludeImportsAsImportPaths(t *testing.T) { t.Parallel() testRunStdout( t, nil, 0, `buf/buf.proto google/protobuf/descriptor.proto`, "ls-files", "--include-imports", "--as-import-paths", filepath.Join("testdata", "success"), ) testRunStdout( t, nil, 0, `buf/buf.proto google/protobuf/descriptor.proto`, "ls-files", "--include-imports", "--as-import-paths", filepath.Join("testdata", "success", "buf", "buf.proto"), ) } func TestLsFilesImage1(t *testing.T) { t.Parallel() stdout := bytes.NewBuffer(nil) testRun( t, 0, nil, stdout, "build", "-o", "-", filepath.Join("testdata", "success"), ) testRunStdout( t, stdout, 0, ` buf/buf.proto `, "ls-files", "-", ) } func TestLsFilesImage1_Yaml(t *testing.T) { t.Parallel() stdout := bytes.NewBuffer(nil) testRun( t, 0, nil, stdout, "build", "-o", "-#format=yaml", filepath.Join("testdata", "success"), ) testRunStdout( t, stdout, 0, ` buf/buf.proto `, "ls-files", "-#format=yaml", ) } func TestLsFilesImage2(t *testing.T) { t.Parallel() stdout := bytes.NewBuffer(nil) testRun( t, 0, nil, stdout, "build", "-o", "-", filepath.Join("testdata", "success"), ) testRunStdout( t, stdout, 0, ` buf/buf.proto google/protobuf/descriptor.proto `, "ls-files", "-", "--include-imports", ) } func TestLsFilesImage3(t *testing.T) { t.Parallel() stdout := bytes.NewBuffer(nil) testRun( t, 0, nil, stdout, "build", "--exclude-imports", "-o", "-", filepath.Join("testdata", "success"), ) testRunStdout( t, stdout, 0, ` buf/buf.proto `, "ls-files", "-", ) } func TestLsFilesImage4(t *testing.T) { t.Parallel() stdout := bytes.NewBuffer(nil) testRun( t, 0, nil, stdout, "build", "-o", "-", filepath.Join("testdata", "success", "buf", "buf.proto"), ) testRunStdout( t, stdout, 0, ` buf/buf.proto `, "ls-files", "-", ) } func TestLsFilesImage5(t *testing.T) { t.Parallel() stdout := bytes.NewBuffer(nil) testRun( t, 0, nil, stdout, "build", "-o", "-", filepath.Join("testdata", "success", "buf", "buf.proto"), ) testRunStdout( t, stdout, 0, ` buf/buf.proto google/protobuf/descriptor.proto `, "ls-files", "--include-imports", "-", ) } func TestBuildFailProtoFileRefWithPathFlag(t *testing.T) { t.Parallel() testRunStdoutStderrNoWarn( t, nil, 1, "", // stdout should be empty `Failure: --path is not valid for use with .proto file references`, "build", filepath.Join("testdata", "success", "buf", "buf.proto"), "--path", filepath.Join("testdata", "success", "buf", "buf.proto"), "-o", "-", ) } func TestImageConvertRoundtripBinaryJSONBinary(t *testing.T) { t.Parallel() stdout := bytes.NewBuffer(nil) testRun( t, 0, nil, stdout, "build", "-o", "-", filepath.Join("testdata", "customoptions1"), ) binary1 := stdout.Bytes() require.NotEmpty(t, binary1) stdin := stdout stdout = bytes.NewBuffer(nil) testRun( t, 0, stdin, stdout, "build", "-", "-o", "-#format=json", ) stdin = stdout stdout = bytes.NewBuffer(nil) testRun( t, 0, stdin, stdout, "build", "-#format=json", "-o", "-", ) require.Equal(t, binary1, stdout.Bytes()) } func TestImageConvertRoundtripJSONBinaryJSON(t *testing.T) { t.Parallel() stdout := bytes.NewBuffer(nil) testRun( t, 0, nil, stdout, "build", "-o", "-#format=json", filepath.Join("testdata", "customoptions1"), ) json1 := stdout.Bytes() require.NotEmpty(t, json1) stdin := stdout stdout = bytes.NewBuffer(nil) testRun( t, 0, stdin, stdout, "build", "-#format=json", "-o", "-", ) stdin = stdout stdout = bytes.NewBuffer(nil) testRun( t, 0, stdin, stdout, "build", "-", "-o", "-#format=json", ) require.Equal(t, json1, stdout.Bytes()) } func TestModInitBasic(t *testing.T) { t.Parallel() testModInit( t, `# For details on buf.yaml configuration, visit https://buf.build/docs/configuration/v2/buf-yaml version: v2 lint: use: - STANDARD breaking: use: - FILE `, false, "", ) } func TestLsFilesOverlappingPaths(t *testing.T) { t.Parallel() // It should be OK to have paths that overlap, and ls-files (and other commands) // should output the union. testRunStdout( t, nil, 0, filepath.FromSlash(`testdata/paths/a/v3/a.proto testdata/paths/a/v3/foo/bar.proto testdata/paths/a/v3/foo/foo.proto`), "ls-files", filepath.Join("testdata", "paths"), "--path", filepath.Join("testdata", "paths", "a", "v3"), "--path", filepath.Join("testdata", "paths", "a", "v3", "foo"), ) } func TestBuildOverlappingPaths(t *testing.T) { t.Parallel() // This may differ from LsFilesOverlappingPaths as we do a build of an image here. // Building of images results in bufmodule.ModuleSetToModuleReadBucketWithOnlyProtoFiles being // called, which is the original source of the issue that resulted in this test. testBuildLsFilesFormatImport( t, 0, []string{ `a/v3/a.proto`, `a/v3/foo/bar.proto`, `a/v3/foo/foo.proto`, }, filepath.Join("testdata", "paths"), "--path", filepath.Join("testdata", "paths", "a", "v3"), "--path", filepath.Join("testdata", "paths", "a", "v3", "foo"), ) } func TestExportProto(t *testing.T) { t.Parallel() tempDir := t.TempDir() testRunStdout( t, nil, 0, ``, "export", "-o", tempDir, filepath.Join("testdata", "export", "proto"), ) readWriteBucket, err := storageos.NewProvider().NewReadWriteBucket(tempDir) require.NoError(t, err) // This should NOT include unimported.proto storagetesting.AssertPaths( t, readWriteBucket, "", "request.proto", "rpc.proto", ) } func TestExportOtherProto(t *testing.T) { t.Parallel() tempDir := t.TempDir() testRunStdout( t, nil, 0, ``, "export", "-o", tempDir, filepath.Join("testdata", "export", "other", "proto"), ) readWriteBucket, err := storageos.NewProvider().NewReadWriteBucket(tempDir) require.NoError(t, err) storagetesting.AssertPaths( t, readWriteBucket, "", "request.proto", "unimported.proto", "another.proto", ) } func TestExportAll(t *testing.T) { t.Parallel() tempDir := t.TempDir() testRunStdout( t, nil, 0, ``, "export", "-o", tempDir, filepath.Join("testdata", "export"), ) readWriteBucket, err := storageos.NewProvider().NewReadWriteBucket(tempDir) require.NoError(t, err) storagetesting.AssertPaths( t, readWriteBucket, "", "another.proto", "request.proto", "rpc.proto", "unimported.proto", ) } func TestExportExcludeImports(t *testing.T) { t.Parallel() tempDir := t.TempDir() testRunStdout( t, nil, 0, ``, "export", "--exclude-imports", "-o", tempDir, filepath.Join("testdata", "export", "proto"), ) readWriteBucket, err := storageos.NewProvider().NewReadWriteBucket(tempDir) require.NoError(t, err) storagetesting.AssertPaths( t, readWriteBucket, "", "rpc.proto", ) } func TestExportPaths(t *testing.T) { t.Parallel() tempDir := t.TempDir() testRunStdout( t, nil, 0, ``, "export", "--path", filepath.Join("testdata", "export", "other", "proto", "request.proto"), "-o", tempDir, filepath.Join("testdata", "export"), ) readWriteBucket, err := storageos.NewProvider().NewReadWriteBucket(tempDir) require.NoError(t, err) storagetesting.AssertPaths( t, readWriteBucket, "", "request.proto", ) } func TestExportPathsAndExcludes(t *testing.T) { t.Parallel() tempDir := t.TempDir() testRunStdout( t, nil, 0, ``, "export", filepath.Join("testdata", "paths"), "--path", filepath.Join("testdata", "paths", "a", "v3"), "--exclude-path", filepath.Join("testdata", "paths", "a", "v3", "foo"), "-o", tempDir, ) readWriteBucket, err := storageos.NewProvider().NewReadWriteBucket(tempDir) require.NoError(t, err) storagetesting.AssertPaths( t, readWriteBucket, "", "a/v3/a.proto", ) storagetesting.AssertNotExist( t, readWriteBucket, "a/v3/foo/foo.proto", ) storagetesting.AssertNotExist( t, readWriteBucket, "a/v3/foo/bar.proto", ) } func TestExportProtoFileRef(t *testing.T) { t.Parallel() tempDir := t.TempDir() testRunStdout( t, nil, 0, ``, "export", "-o", tempDir, filepath.Join("testdata", "export", "proto", "rpc.proto"), ) readWriteBucket, err := storageos.NewProvider().NewReadWriteBucket(tempDir) require.NoError(t, err) storagetesting.AssertPaths( t, readWriteBucket, "", "request.proto", "rpc.proto", ) } func TestExportProtoFileRefExcludeImports(t *testing.T) { t.Parallel() tempDir := t.TempDir() testRunStdout( t, nil, 0, ``, "export", "--exclude-imports", "-o", tempDir, filepath.Join("testdata", "export", "proto", "rpc.proto"), ) readWriteBucket, err := storageos.NewProvider().NewReadWriteBucket(tempDir) require.NoError(t, err) storagetesting.AssertPaths( t, readWriteBucket, "", "rpc.proto", ) } func TestExportProtoFileRefIncludePackageFiles(t *testing.T) { t.Parallel() tempDir := t.TempDir() testRunStdout( t, nil, 0, ``, "export", "-o", tempDir, fmt.Sprintf("%s#include_package_files=true", filepath.Join("testdata", "export", "other", "proto", "request.proto")), ) readWriteBucket, err := storageos.NewProvider().NewReadWriteBucket(tempDir) require.NoError(t, err) storagetesting.AssertPaths( t, readWriteBucket, "", "request.proto", "unimported.proto", "another.proto", ) } func TestExportProtoFileRefIncludePackageFilesExcludeImports(t *testing.T) { t.Parallel() tempDir := t.TempDir() testRunStdout( t, nil, 0, ``, "export", "--exclude-imports", "-o", tempDir, fmt.Sprintf("%s#include_package_files=true", filepath.Join("testdata", "export", "other", "proto", "request.proto")), ) readWriteBucket, err := storageos.NewProvider().NewReadWriteBucket(tempDir) require.NoError(t, err) storagetesting.AssertPaths( t, readWriteBucket, "", "request.proto", "unimported.proto", ) } func TestExportProtoFileRefWithPathFlag(t *testing.T) { t.Parallel() tempDir := t.TempDir() testRunStdoutStderrNoWarn( t, nil, 1, "", // stdout should be empty `Failure: --path is not valid for use with .proto file references`, "export", filepath.Join("testdata", "protofileref", "success", "buf.proto"), "-o", tempDir, "--path", filepath.Join("testdata", "protofileref", "success", "buf.proto"), ) } func TestExportAllSourceFilesV1Module(t *testing.T) { t.Parallel() tempDir := t.TempDir() testRunStdout( t, nil, 0, ``, "export", "--all", "-o", tempDir, filepath.Join("testdata", "export", "proto"), ) readWriteBucket, err := storageos.NewProvider().NewReadWriteBucket(tempDir) require.NoError(t, err) storagetesting.AssertPaths( t, readWriteBucket, "", "LICENSE", "README.md", "request.proto", "rpc.proto", ) } func TestExportAllSourceFilesV1Workspace(t *testing.T) { t.Parallel() tempDir := t.TempDir() testRunStdout( t, nil, 0, ``, "export", "--all", "-o", tempDir, filepath.Join("testdata", "export"), ) readWriteBucket, err := storageos.NewProvider().NewReadWriteBucket(tempDir) require.NoError(t, err) storagetesting.AssertPaths( t, readWriteBucket, "", "LICENSE.request", "LICENSE.rpc", "README.another.md", "README.rpc.md", "another.proto", "request.proto", "rpc.proto", "unimported.proto", ) } func TestExportAllSourceFilesV2Module(t *testing.T) { t.Parallel() tempDir := t.TempDir() testRunStdout( t, nil, 0, ``, "export", "--all", "-o", tempDir, filepath.Join("testdata", "workspace", "success", "v2", "export", "proto"), ) readWriteBucket, err := storageos.NewProvider().NewReadWriteBucket(tempDir) require.NoError(t, err) storagetesting.AssertPaths( t, readWriteBucket, "", "LICENSE", "README.md", "request.proto", "rpc.proto", ) } func TestExportAllSourceFilesV2Workspace(t *testing.T) { t.Parallel() tempDir := t.TempDir() testRunStdout( t, nil, 0, ``, "export", "--all", "-o", tempDir, filepath.Join("testdata", "workspace", "success", "v2", "export"), ) readWriteBucket, err := storageos.NewProvider().NewReadWriteBucket(tempDir) require.NoError(t, err) storagetesting.AssertPaths( t, readWriteBucket, "", "LICENSE.request", "LICENSE.rpc", "README.another.md", "README.rpc.md", "another.proto", "request.proto", "rpc.proto", "unimported.proto", ) } func TestBuildWithPaths(t *testing.T) { t.Parallel() testRunStdout(t, nil, 0, ``, "build", filepath.Join("testdata", "paths"), "--path", filepath.Join("testdata", "paths", "a", "v3"), "--exclude-path", filepath.Join("testdata", "paths", "a", "v3", "foo")) testRunStdoutStderrNoWarn( t, nil, 1, ``, // This is new post-refactor. Before, we gave precedence to --path. While a change, // doing --path foo/bar --exclude-path foo seems like a bug rather than expected behavior to maintain. filepath.FromSlash(`Failure: excluded path "testdata/paths/a/v3" contains targeted path "testdata/paths/a/v3/foo", which means all paths in "testdata/paths/a/v3/foo" will be excluded`), "build", filepath.Join("testdata", "paths"), "--path", filepath.Join("testdata", "paths", "a", "v3", "foo"), "--exclude-path", filepath.Join("testdata", "paths", "a", "v3"), ) } func TestLintWithPaths(t *testing.T) { t.Parallel() testRunStdoutStderrNoWarn( t, nil, bufctl.ExitCodeFileAnnotation, filepath.FromSlash(`testdata/paths/a/v3/a.proto:7:10:Field name "Value" should be lower_snake_case, such as "value".`), "", "lint", filepath.Join("testdata", "paths"), "--path", filepath.Join("testdata", "paths", "a", "v3"), "--exclude-path", filepath.Join("testdata", "paths", "a", "v3", "foo"), ) testRunStdoutStderrNoWarn( t, nil, 1, "", // This is new post-refactor. Before, we gave precedence to --path. While a change, // doing --path foo/bar --exclude-path foo seems like a bug rather than expected behavior to maintain. filepath.FromSlash(`Failure: excluded path "testdata/paths/a/v3" contains targeted path "testdata/paths/a/v3/foo", which means all paths in "testdata/paths/a/v3/foo" will be excluded`), "lint", filepath.Join("testdata", "paths"), "--path", filepath.Join("testdata", "paths", "a", "v3", "foo"), "--exclude-path", filepath.Join("testdata", "paths", "a", "v3"), ) } func TestLintWithPlugins(t *testing.T) { t.Parallel() // defaults only, comment ignores on. testRunStdout( t, nil, bufctl.ExitCodeFileAnnotation, filepath.FromSlash(` testdata/check_plugins/current/proto/api/v1/service.proto:11:1:Service name "api.v1.FooServiceMock" has banned suffix "Mock". (buf-plugin-suffix) testdata/check_plugins/current/proto/api/v1/service.proto:12:14:RPC request type "GetFooMockRequest" should be named "GetFooRequest" or "FooServiceMockGetFooRequest". testdata/check_plugins/current/proto/api/v1/service.proto:12:42:RPC response type "GetFooMockResponse" should be named "GetFooResponse" or "FooServiceMockGetFooResponse". testdata/check_plugins/current/proto/api/v1/service.proto:16:9:Service name "FooServiceTest" should be suffixed with "Service". testdata/check_plugins/current/proto/api/v1/service.proto:17:14:RPC request type "GetFooTestRequest" should be named "GetFooRequest" or "FooServiceTestGetFooRequest". testdata/check_plugins/current/proto/api/v1/service.proto:17:42:RPC response type "GetFooTestResponse" should be named "GetFooResponse" or "FooServiceTestGetFooResponse". testdata/check_plugins/current/proto/api/v1/service.proto:26:1:"ListFooResponse" is a pagination response without a page token field named "page_token" (buf-plugin-rpc-ext) testdata/check_plugins/current/proto/common/v1alpha1/messages.proto:16:5:field "common.v1alpha1.Four.FourTwo.id" does not have rule (buf.validate.field).string.tuuid set (buf-plugin-protovalidate-ext) testdata/check_plugins/current/vendor/protovalidate/buf/validate/validate.proto:95:3:field "buf.validate.Rule.id" does not have rule (buf.validate.field).string.tuuid set (buf-plugin-protovalidate-ext) `), "lint", filepath.Join("testdata", "check_plugins", "current"), ) // Defaults only, comment ignores off. // Always ignore the vendored protovalidate module. // // There are still lint failures for protovalidate despite being set as an ignore path because // proto imports these files, and paths outside of a module cannot be configured as ignore paths // for the module -- this is expected behavior for now. testRunStdout( t, nil, bufctl.ExitCodeFileAnnotation, filepath.FromSlash(` testdata/check_plugins/current/proto/api/v1/service.proto:11:1:Service name "api.v1.FooServiceMock" has banned suffix "Mock". (buf-plugin-suffix) testdata/check_plugins/current/proto/api/v1/service.proto:11:9:Service name "FooServiceMock" should be suffixed with "Service". testdata/check_plugins/current/proto/api/v1/service.proto:12:14:RPC request type "GetFooMockRequest" should be named "GetFooRequest" or "FooServiceMockGetFooRequest". testdata/check_plugins/current/proto/api/v1/service.proto:12:42:RPC response type "GetFooMockResponse" should be named "GetFooResponse" or "FooServiceMockGetFooResponse". testdata/check_plugins/current/proto/api/v1/service.proto:16:1:Service name "api.v1.FooServiceTest" has banned suffix "Test". (buf-plugin-suffix) testdata/check_plugins/current/proto/api/v1/service.proto:16:9:Service name "FooServiceTest" should be suffixed with "Service". testdata/check_plugins/current/proto/api/v1/service.proto:17:14:RPC request type "GetFooTestRequest" should be named "GetFooRequest" or "FooServiceTestGetFooRequest". testdata/check_plugins/current/proto/api/v1/service.proto:17:42:RPC response type "GetFooTestResponse" should be named "GetFooResponse" or "FooServiceTestGetFooResponse". testdata/check_plugins/current/proto/api/v1/service.proto:26:1:"ListFooResponse" is a pagination response without a page token field named "page_token" (buf-plugin-rpc-ext) testdata/check_plugins/current/proto/common/v1alpha1/messages.proto:16:5:field "common.v1alpha1.Four.FourTwo.id" does not have rule (buf.validate.field).string.tuuid set (buf-plugin-protovalidate-ext) testdata/check_plugins/current/vendor/protovalidate/buf/validate/validate.proto:95:3:field "buf.validate.Rule.id" does not have rule (buf.validate.field).string.tuuid set (buf-plugin-protovalidate-ext) `), "lint", filepath.Join("testdata", "check_plugins", "current"), "--config", `{ "version":"v2", "modules": [ {"path": "testdata/check_plugins/current/proto"}, {"path": "testdata/check_plugins/current/vendor/protovalidate"} ], "lint": { "disallow_comment_ignores": true, "ignore": [ "testdata/check_plugins/current/vendor/protovalidate", "testdata/check_plugins/current/proto/common/v1/breaking.proto", "testdata/check_plugins/current/proto/common/v1alpha1/breaking.proto" ] }, "plugins":[ { "plugin": "buf-plugin-suffix", "options": { "service_banned_suffixes": ["Mock", "Test"], "rpc_banned_suffixes": ["Element"], "field_banned_suffixes": ["_uuid"], "enum_value_banned_suffixes": ["_invalid"], "service_no_change_suffixes": ["Service"], "message_no_change_suffixes": ["Request", "Response"], "enum_no_change_suffixes": ["State"] } }, {"plugin": "buf-plugin-protovalidate-ext"}, {"plugin": "buf-plugin-rpc-ext"} ] }`, ) // With specified use, ignore, and ignore_only configurations. // Always ignore the vendored protovalidate module. // // There are still lint failures for protovalidate despite being set as an ignore path because // proto imports these files, and paths outside of a module cannot be configured as ignore paths // for the module -- this is expected behavior for now. testRunStdout( t, nil, bufctl.ExitCodeFileAnnotation, filepath.FromSlash(` testdata/check_plugins/current/proto/api/v1/service.proto:11:1:Service name "api.v1.FooServiceMock" has banned suffix "Mock". (buf-plugin-suffix) testdata/check_plugins/current/proto/common/v1alpha1/messages.proto:16:5:field "common.v1alpha1.Four.FourTwo.id" does not have rule (buf.validate.field).string.tuuid set (buf-plugin-protovalidate-ext) testdata/check_plugins/current/vendor/protovalidate/buf/validate/validate.proto:95:3:field "buf.validate.Rule.id" does not have rule (buf.validate.field).string.tuuid set (buf-plugin-protovalidate-ext) `), "lint", filepath.Join("testdata", "check_plugins", "current"), "--config", `{ "version":"v2", "modules": [ {"path": "testdata/check_plugins/current/proto"}, {"path": "testdata/check_plugins/current/vendor/protovalidate"} ], "lint": { "use": ["PAGE_REQUEST_HAS_TOKEN", "SERVICE_BANNED_SUFFIXES", "VALIDATE_ID_DASHLESS"], "ignore": [ "testdata/check_plugins/current/vendor/protovalidate", "testdata/check_plugins/current/proto/common/v1/breaking.proto", "testdata/check_plugins/current/proto/common/v1alpha1/breaking.proto" ], "ignore_only": { "VALIDATE_ID_DASHLESS": ["testdata/check_plugins/current/vendor/protovalidate/buf/validate"], } }, "plugins":[ { "plugin": "buf-plugin-suffix", "options": { "service_banned_suffixes": ["Mock", "Test"], "rpc_banned_suffixes": ["Element"], "field_banned_suffixes": ["_uuid"], "enum_value_banned_suffixes": ["_invalid"], "service_no_change_suffixes": ["Service"], "message_no_change_suffixes": ["Request", "Response"], "enum_no_change_suffixes": ["State"] } }, {"plugin": "buf-plugin-protovalidate-ext"}, {"plugin": "buf-plugin-rpc-ext"} ] }`, ) // Set the same category for lint and breaking, but ensure that only the lint rules run. testRunStdout( t, nil, bufctl.ExitCodeFileAnnotation, filepath.FromSlash(` testdata/check_plugins/current/proto/common/v1/common.proto:8:5:Field name "common.v1.One.Two.foo_uuid" has banned suffix "_uuid". (buf-plugin-suffix) `), "lint", filepath.Join("testdata", "check_plugins", "current"), "--config", `{ "version":"v2", "modules": [ {"path": "testdata/check_plugins/current/proto"}, {"path": "testdata/check_plugins/current/vendor/protovalidate"} ], "lint": { "use": ["ATTRIBUTES_SUFFIXES"] }, "breaking": { "use": ["ATTRIBUTES_SUFFIXES"] }, "plugins":[ { "plugin": "buf-plugin-suffix", "options": { "service_banned_suffixes": ["Mock", "Test"], "rpc_banned_suffixes": ["Element"], "field_banned_suffixes": ["_uuid"], "enum_value_banned_suffixes": ["_invalid"], "service_no_change_suffixes": ["Service"], "message_no_change_suffixes": ["DONT_CHANGE"], "enum_no_change_suffixes": ["DO_NOT_CHANGE"] } }, {"plugin": "buf-plugin-protovalidate-ext"}, {"plugin": "buf-plugin-rpc-ext"} ] }`, ) // tests that if a plugin panics, the buf CLI does not panic require.NotPanics( t, func() { appcmdtesting.Run( t, newRootCommand, appcmdtesting.WithEnv(internaltesting.NewEnvFunc(t)), appcmdtesting.WithExpectedExitCode(1), appcmdtesting.WithExpectedStderrPartials( `panic: this panic is intentional`, `Failure: plugin "buf-plugin-panic" failed: Exited with code 2: exit status 2`, ), appcmdtesting.WithArgs( "lint", filepath.Join("testdata", "check_plugins", "current"), "--config", `{ "version":"v2", "modules": [ {"path": "testdata/check_plugins/current/proto"}, {"path": "testdata/check_plugins/current/vendor/protovalidate"} ], "lint": { "use": ["LINT_PANIC"] }, "plugins":[ {"plugin": "buf-plugin-panic"} ] }`, ), ) }, ) } func TestBreakingWithPaths(t *testing.T) { t.Parallel() tempDir := t.TempDir() testRunStdout(t, nil, 0, ``, "build", filepath.Join("internal", "command", "generate", "testdata", "paths"), "-o", filepath.Join(tempDir, "previous.binpb")) testRunStdout(t, nil, 0, ``, "build", filepath.Join("testdata", "paths"), "-o", filepath.Join(tempDir, "current.binpb")) readWriteBucket, err := storageos.NewProvider().NewReadWriteBucket(tempDir) require.NoError(t, err) storagetesting.AssertPaths( t, readWriteBucket, "", "previous.binpb", "current.binpb", ) testRunStdoutStderrNoWarn( t, nil, bufctl.ExitCodeFileAnnotation, `a/v3/a.proto:6:3:Field "1" with name "key" on message "Foo" changed type from "string" to "int32". a/v3/a.proto:7:3:Field "2" with name "Value" on message "Foo" changed option "json_name" from "value" to "Value". a/v3/a.proto:7:10:Field "2" on message "Foo" changed name from "value" to "Value".`, "", "breaking", filepath.Join(tempDir, "current.binpb"), "--against", filepath.Join(tempDir, "previous.binpb"), "--path", filepath.Join("a", "v3"), "--exclude-path", filepath.Join("a", "v3", "foo"), ) testRunStdoutStderrNoWarn( t, nil, bufctl.ExitCodeFileAnnotation, `a/v3/a.proto:6:3:Field "1" with name "key" on message "Foo" changed type from "string" to "int32". See https://developers.google.com/protocol-buffers/docs/proto3#updating for wire compatibility rules.`, "", "breaking", filepath.Join(tempDir, "current.binpb"), "--against", filepath.Join(tempDir, "previous.binpb"), "--path", filepath.Join("a", "v3"), "--exclude-path", filepath.Join("a", "v3", "foo"), "--config", `{"version":"v2","breaking":{"use":["WIRE"]}}`, ) } func TestBreakingWithPlugins(t *testing.T) { t.Parallel() currentConfig := `{ "version":"v2", "modules": [ {"path": "testdata/check_plugins/current/proto"}, {"path": "testdata/check_plugins/current/vendor/protovalidate"} ], "breaking": { "use": ["STRING_LEN_RANGE_NO_SHRINK"] }, "plugins":[ { "plugin": "buf-plugin-suffix", "options": { "service_banned_suffixes": ["Mock", "Test"], "rpc_banned_suffixes": ["Element"], "field_banned_suffixes": ["_uuid"], "enum_value_banned_suffixes": ["_invalid"], "service_no_change_suffixes": ["Service"], "message_no_change_suffixes": ["Request", "Response"], "enum_no_change_suffixes": ["State"] } }, {"plugin": "buf-plugin-protovalidate-ext"}, {"plugin": "buf-plugin-rpc-ext"} ] }` previousConfig := strings.ReplaceAll( currentConfig, "current", "previous", ) testRunStdout( t, nil, bufctl.ExitCodeFileAnnotation, filepath.FromSlash(` testdata/check_plugins/current/proto/common/v1/breaking.proto:10:5:max len requirement reduced from 10 to 5 (buf-plugin-protovalidate-ext) testdata/check_plugins/current/proto/common/v1alpha1/breaking.proto:10:5:max len requirement reduced from 10 to 5 (buf-plugin-protovalidate-ext) `), "breaking", filepath.Join("testdata", "check_plugins", "current", "proto"), "--against", filepath.Join("testdata", "check_plugins", "previous", "proto"), "--config", currentConfig, "--against-config", previousConfig, ) // ignore unstable package currentConfig = `{ "version":"v2", "modules": [ {"path": "testdata/check_plugins/current/proto"}, {"path": "testdata/check_plugins/current/vendor/protovalidate"} ], "breaking": { "use": ["STRING_LEN_RANGE_NO_SHRINK"], "ignore_unstable_packages": true }, "plugins":[ { "plugin": "buf-plugin-suffix", "options": { "service_banned_suffixes": ["Mock", "Test"], "rpc_banned_suffixes": ["Element"], "field_banned_suffixes": ["_uuid"], "enum_value_banned_suffixes": ["_invalid"], "service_no_change_suffixes": ["Service"], "message_no_change_suffixes": ["Request", "Response"], "enum_no_change_suffixes": ["State"] } }, {"plugin": "buf-plugin-protovalidate-ext"}, {"plugin": "buf-plugin-rpc-ext"} ] }` previousConfig = strings.ReplaceAll( currentConfig, "current", "previous", ) testRunStdout( t, nil, bufctl.ExitCodeFileAnnotation, filepath.FromSlash(` testdata/check_plugins/current/proto/common/v1/breaking.proto:10:5:max len requirement reduced from 10 to 5 (buf-plugin-protovalidate-ext) `), "breaking", filepath.Join("testdata", "check_plugins", "current", "proto"), "--against", filepath.Join("testdata", "check_plugins", "previous", "proto"), "--config", currentConfig, "--against-config", previousConfig, ) // use category currentConfig = `{ "version":"v2", "modules": [ {"path": "testdata/check_plugins/current/proto"}, {"path": "testdata/check_plugins/current/vendor/protovalidate"} ], "breaking": { "use": ["ATTRIBUTES_SUFFIXES"], }, "plugins":[ { "plugin": "buf-plugin-suffix", "options": { "message_no_change_suffixes": ["DONT_CHANGE"], "enum_no_change_suffixes": ["DO_NOT_CHANGE"] } }, ] }` previousConfig = strings.ReplaceAll( currentConfig, "current", "previous", ) testRunStdout( t, nil, bufctl.ExitCodeFileAnnotation, filepath.FromSlash(` testdata/check_plugins/current/proto/common/v1/breaking.proto:14:1:Message "common.v1.MSG_DONT_CHANGE" has a suffix configured for no changes has different fields, previously [], currently [common.v1.MSG_DONT_CHANGE.new_field]. (buf-plugin-suffix) testdata/check_plugins/current/proto/common/v1/breaking.proto:18:1:Enum "common.v1.E_DO_NOT_CHANGE" has a suffix configured for no changes has different enum values, previously [common.v1.ZERO], currently [common.v1.ONE common.v1.ZERO]. (buf-plugin-suffix) `), "breaking", filepath.Join("testdata", "check_plugins", "current", "proto"), "--against", filepath.Join("testdata", "check_plugins", "previous", "proto"), "--config", currentConfig, "--against-config", previousConfig, ) // use deprecated category currentConfig = `{ "version":"v2", "modules": [ {"path": "testdata/check_plugins/current/proto"}, {"path": "testdata/check_plugins/current/vendor/protovalidate"} ], "breaking": { "use": ["RESOURCE_SUFFIXES"], }, "plugins":[ { "plugin": "buf-plugin-suffix", "options": { "message_no_change_suffixes": ["DONT_CHANGE"], "enum_no_change_suffixes": ["DO_NOT_CHANGE"] } }, ] }` previousConfig = strings.ReplaceAll( currentConfig, "current", "previous", ) testRunStdout( t, nil, bufctl.ExitCodeFileAnnotation, filepath.FromSlash(` testdata/check_plugins/current/proto/common/v1/breaking.proto:14:1:Message "common.v1.MSG_DONT_CHANGE" has a suffix configured for no changes has different fields, previously [], currently [common.v1.MSG_DONT_CHANGE.new_field]. (buf-plugin-suffix) testdata/check_plugins/current/proto/common/v1/breaking.proto:18:1:Enum "common.v1.E_DO_NOT_CHANGE" has a suffix configured for no changes has different enum values, previously [common.v1.ZERO], currently [common.v1.ONE common.v1.ZERO]. (buf-plugin-suffix) `), "breaking", filepath.Join("testdata", "check_plugins", "current", "proto"), "--against", filepath.Join("testdata", "check_plugins", "previous", "proto"), "--config", currentConfig, "--against-config", previousConfig, ) // use except currentConfig = `{ "version":"v2", "modules": [ {"path": "testdata/check_plugins/current/proto"}, {"path": "testdata/check_plugins/current/vendor/protovalidate"} ], "breaking": { "use": ["RESOURCE_SUFFIXES"], "except": ["ENUM_SUFFIXES_NO_CHANGE"] }, "plugins":[ { "plugin": "buf-plugin-suffix", "options": { "message_no_change_suffixes": ["DONT_CHANGE"], "enum_no_change_suffixes": ["DO_NOT_CHANGE"] } }, ] }` previousConfig = strings.ReplaceAll( currentConfig, "current", "previous", ) testRunStdout( t, nil, bufctl.ExitCodeFileAnnotation, filepath.FromSlash(` testdata/check_plugins/current/proto/common/v1/breaking.proto:14:1:Message "common.v1.MSG_DONT_CHANGE" has a suffix configured for no changes has different fields, previously [], currently [common.v1.MSG_DONT_CHANGE.new_field]. (buf-plugin-suffix) `), "breaking", filepath.Join("testdata", "check_plugins", "current", "proto"), "--against", filepath.Join("testdata", "check_plugins", "previous", "proto"), "--config", currentConfig, "--against-config", previousConfig, ) // ignore module currentConfig = `{ "version":"v2", "modules": [ {"path": "testdata/check_plugins/current/proto"}, {"path": "testdata/check_plugins/current/vendor/protovalidate"} ], "breaking": { "use": ["RESOURCE_SUFFIXES"], "ignore": ["testdata/check_plugins/current/proto"] }, "plugins":[ { "plugin": "buf-plugin-suffix", "options": { "message_no_change_suffixes": ["DONT_CHANGE"], "enum_no_change_suffixes": ["DO_NOT_CHANGE"] } }, ] }` previousConfig = strings.ReplaceAll( currentConfig, "current", "previous", ) testRunStdout( t, nil, 0, "", "breaking", filepath.Join("testdata", "check_plugins", "current", "proto"), "--against", filepath.Join("testdata", "check_plugins", "previous", "proto"), "--config", currentConfig, "--against-config", previousConfig, ) // ignore path inside module currentConfig = `{ "version":"v2", "modules": [ {"path": "testdata/check_plugins/current/proto"}, {"path": "testdata/check_plugins/current/vendor/protovalidate"} ], "breaking": { "use": ["RESOURCE_SUFFIXES"], "ignore": ["testdata/check_plugins/current/proto/common/v1"] }, "plugins":[ { "plugin": "buf-plugin-suffix", "options": { "message_no_change_suffixes": ["DONT_CHANGE"], "enum_no_change_suffixes": ["DO_NOT_CHANGE"] } }, ] }` previousConfig = strings.ReplaceAll( currentConfig, "current", "previous", ) testRunStdout( t, nil, 0, "", "breaking", filepath.Join("testdata", "check_plugins", "current", "proto"), "--against", filepath.Join("testdata", "check_plugins", "previous", "proto"), "--config", currentConfig, "--against-config", previousConfig, ) // ignore only currentConfig = `{ "version":"v2", "modules": [ {"path": "testdata/check_plugins/current/proto"}, {"path": "testdata/check_plugins/current/vendor/protovalidate"} ], "breaking": { "use": ["RESOURCE_SUFFIXES"], "ignore_only": { "MESSAGE_SUFFIXES_NO_CHANGE": ["testdata/check_plugins/current/proto/common"], }, }, "plugins":[ { "plugin": "buf-plugin-suffix", "options": { "message_no_change_suffixes": ["DONT_CHANGE"], "enum_no_change_suffixes": ["DO_NOT_CHANGE"] } }, ] }` previousConfig = strings.ReplaceAll( currentConfig, "current", "previous", ) testRunStdout( t, nil, bufctl.ExitCodeFileAnnotation, filepath.FromSlash(` testdata/check_plugins/current/proto/common/v1/breaking.proto:18:1:Enum "common.v1.E_DO_NOT_CHANGE" has a suffix configured for no changes has different enum values, previously [common.v1.ZERO], currently [common.v1.ONE common.v1.ZERO]. (buf-plugin-suffix) `), "breaking", filepath.Join("testdata", "check_plugins", "current", "proto"), "--against", filepath.Join("testdata", "check_plugins", "previous", "proto"), "--config", currentConfig, "--against-config", previousConfig, ) // Set the same category for lint and breaking but ensure only breaking is run. currentConfig = `{ "version":"v2", "modules": [ {"path": "testdata/check_plugins/current/proto"}, {"path": "testdata/check_plugins/current/vendor/protovalidate"} ], "lint": { "use": ["ATTRIBUTES_SUFFIXES"] }, "breaking": { "use": ["ATTRIBUTES_SUFFIXES"] }, "plugins":[ { "plugin": "buf-plugin-suffix", "options": { "service_banned_suffixes": ["Mock", "Test"], "rpc_banned_suffixes": ["Element"], "field_banned_suffixes": ["_uuid"], "enum_value_banned_suffixes": ["_invalid"], "service_no_change_suffixes": ["Service"], "message_no_change_suffixes": ["DONT_CHANGE"], "enum_no_change_suffixes": ["DO_NOT_CHANGE"] } }, ] }` previousConfig = strings.ReplaceAll( currentConfig, "current", "previous", ) testRunStdout( t, nil, bufctl.ExitCodeFileAnnotation, filepath.FromSlash(` testdata/check_plugins/current/proto/common/v1/breaking.proto:14:1:Message "common.v1.MSG_DONT_CHANGE" has a suffix configured for no changes has different fields, previously [], currently [common.v1.MSG_DONT_CHANGE.new_field]. (buf-plugin-suffix) testdata/check_plugins/current/proto/common/v1/breaking.proto:18:1:Enum "common.v1.E_DO_NOT_CHANGE" has a suffix configured for no changes has different enum values, previously [common.v1.ZERO], currently [common.v1.ONE common.v1.ZERO]. (buf-plugin-suffix) `), "breaking", filepath.Join("testdata", "check_plugins", "current", "proto"), "--against", filepath.Join("testdata", "check_plugins", "previous", "proto"), "--config", currentConfig, "--against-config", previousConfig, ) } func TestBreakingAgainstRegistryFlag(t *testing.T) { t.Parallel() testRunStderr( t, nil, 1, "Failure: Cannot set both --against and --against-registry", "breaking", filepath.Join("testdata", "check_plugins", "current", "proto"), "--against", filepath.Join("testdata", "check_plugins", "previous", "proto"), "--against-registry", ) testRunStderr( t, nil, 1, "Failure: cannot use --against-registry with unnamed module, testdata/success", "breaking", filepath.Join("testdata", "success"), "--against-registry", ) } func TestVersion(t *testing.T) { t.Parallel() testRunStdout(t, nil, 0, bufcli.Version, "--version") } func TestConvertWithImage(t *testing.T) { t.Parallel() tempDir := t.TempDir() testRunStdout( t, nil, 0, ``, "build", filepath.Join("testdata", "success"), "-o", filepath.Join(tempDir, "image.binpb"), ) t.Run("stdin input", func(t *testing.T) { t.Parallel() stdin, err := os.Open(filepath.Join(convertTestDataDir, "descriptor.plain.binpb")) require.NoError(t, err) defer stdin.Close() stdout := bytes.NewBuffer(nil) testRun( t, 0, stdin, stdout, "convert", filepath.Join(tempDir, "image.binpb"), "--type", "buf.Foo", ) assert.JSONEq(t, `{"one":"55"}`, stdout.String()) }) t.Run("no stdin input from binpb", func(t *testing.T) { t.Parallel() testRun( t, 0, nil, nil, "convert", filepath.Join(tempDir, "image.binpb"), "--type", "buf.Foo", "--from=-#format=binpb", "--to=-#format=txtpb", ) }) t.Run("no stdin input from txtpb", func(t *testing.T) { t.Parallel() testRun( t, 0, nil, nil, "convert", filepath.Join(tempDir, "image.binpb"), "--type", "buf.Foo", "--from=-#format=txtpb", "--to=-#format=binpb", ) }) t.Run("no stdin input from yaml", func(t *testing.T) { t.Parallel() testRun( t, 0, nil, nil, "convert", filepath.Join(tempDir, "image.binpb"), "--type", "buf.Foo", "--from=-#format=yaml", "--to=-#format=binpb", ) }) t.Run("no stdin input from json", func(t *testing.T) { t.Parallel() testRunStderrContainsNoWarn( t, nil, 1, []string{ "Failure: --from:", "json unmarshal:", "proto:", "syntax error (line 1:1): unexpected token", }, "convert", filepath.Join(tempDir, "image.binpb"), "--type", "buf.Foo", "--from=-#format=json", "--to=-#format=binpb", ) }) } func TestConvertOutput(t *testing.T) { t.Parallel() tempDir := t.TempDir() testRunStdout( t, nil, 0, ``, "build", filepath.Join("testdata", "success"), "-o", filepath.Join(tempDir, "image.binpb"), ) t.Run("json file output", func(t *testing.T) { t.Parallel() stdin, err := os.Open(filepath.Join(convertTestDataDir, "descriptor.plain.binpb")) require.NoError(t, err) defer stdin.Close() outputTempDir := t.TempDir() testRunStdout( t, stdin, 0, ``, "convert", filepath.Join(tempDir, "image.binpb"), "--type", "buf.Foo", "--to", filepath.Join(outputTempDir, "result.json"), ) readWriteBucket, err := storageos.NewProvider().NewReadWriteBucket(outputTempDir) require.NoError(t, err) storagetesting.AssertPathToContent( t, readWriteBucket, "", map[string]string{ "result.json": `{"one":"55"}`, }, ) }) t.Run("txt file output", func(t *testing.T) { t.Parallel() stdin, err := os.Open(filepath.Join(convertTestDataDir, "descriptor.plain.binpb")) require.NoError(t, err) defer stdin.Close() outputTempDir := t.TempDir() testRunStdout( t, stdin, 0, ``, "convert", filepath.Join(tempDir, "image.binpb"), "--type", "buf.Foo", "--to", filepath.Join(outputTempDir, "result.txt"), ) readWriteBucket, err := storageos.NewProvider().NewReadWriteBucket(outputTempDir) require.NoError(t, err) storagetesting.AssertPathToContent( t, readWriteBucket, "", map[string]string{ "result.txt": `{"one":"55"}`, }, ) }) t.Run("stdout with dash", func(t *testing.T) { t.Parallel() stdin, err := os.Open(filepath.Join(convertTestDataDir, "descriptor.plain.binpb")) require.NoError(t, err) defer stdin.Close() stdout := bytes.NewBuffer(nil) testRun( t, 0, stdin, stdout, "convert", filepath.Join(tempDir, "image.binpb"), "--type", "buf.Foo", "--to", "-", ) assert.JSONEq(t, `{"one":"55"}`, stdout.String()) }) } func TestConvertInvalidTypeName(t *testing.T) { t.Parallel() tempDir := t.TempDir() testRunStdout( t, nil, 0, ``, "build", filepath.Join("testdata", "success"), "-o", filepath.Join(tempDir, "image.binpb"), ) stdin, err := os.Open(filepath.Join(convertTestDataDir, "descriptor.plain.binpb")) require.NoError(t, err) defer stdin.Close() testRunStdoutStderrNoWarn( t, stdin, 1, "", `Failure: --from: ".foo" is not a valid fully qualified type name`, "convert", filepath.Join(tempDir, "image.binpb"), "--type", ".foo", ) } func TestConvert(t *testing.T) { t.Parallel() t.Run("binpb-to-json-file-proto", func(t *testing.T) { t.Parallel() testRunStdoutFile(t, nil, 0, convertTestDataDir+"/bin_json/payload.json", "convert", "--type=buf.Foo", "--from="+convertTestDataDir+"/bin_json/payload.binpb", convertTestDataDir+"/bin_json/buf.proto", ) }) t.Run("json-to-binpb-file-proto", func(t *testing.T) { t.Parallel() testRunStdoutFile(t, nil, 0, convertTestDataDir+"/bin_json/payload.binpb", "convert", "--type=buf.Foo", "--from="+convertTestDataDir+"/bin_json/payload.json", convertTestDataDir+"/bin_json/buf.proto", ) }) t.Run("stdin-json-to-binpb-proto", func(t *testing.T) { t.Parallel() testRunStdoutFile(t, strings.NewReader(`{"one":"55"}`), 0, convertTestDataDir+"/bin_json/payload.binpb", "convert", "--type=buf.Foo", "--from", "-#format=json", convertTestDataDir+"/bin_json/buf.proto", ) }) t.Run("stdin-binpb-to-json-proto", func(t *testing.T) { t.Parallel() file, err := os.Open(convertTestDataDir + "/bin_json/payload.binpb") require.NoError(t, err) testRunStdoutFile(t, file, 0, convertTestDataDir+"/bin_json/payload.json", "convert", "--type=buf.Foo", "--from", "-#format=binpb", convertTestDataDir+"/bin_json/buf.proto", ) }) t.Run("stdin-json-to-json-proto", func(t *testing.T) { t.Parallel() testRunStdoutFile(t, strings.NewReader(`{"one":"55"}`), 0, convertTestDataDir+"/bin_json/payload.json", "convert", "--type=buf.Foo", convertTestDataDir+"/bin_json/buf.proto", "--from", "-#format=json", "--to", "-#format=json") }) t.Run("stdin-input-to-json-image", func(t *testing.T) { t.Parallel() file, err := os.Open(convertTestDataDir + "/bin_json/image.binpb") require.NoError(t, err) testRunStdoutFile(t, file, 0, convertTestDataDir+"/bin_json/payload.json", "convert", "--type=buf.Foo", "-", "--from="+convertTestDataDir+"/bin_json/payload.binpb", "--to", "-#format=json", ) }) t.Run("stdin-json-to-json-image", func(t *testing.T) { t.Parallel() file, err := os.Open(convertTestDataDir + "/bin_json/payload.binpb") require.NoError(t, err) testRunStdoutFile(t, file, 0, convertTestDataDir+"/bin_json/payload.json", "convert", "--type=buf.Foo", convertTestDataDir+"/bin_json/image.binpb", "--from", "-#format=binpb", "--to", "-#format=json") }) t.Run("stdin-binpb-payload-to-json-with-image", func(t *testing.T) { t.Parallel() file, err := os.Open(convertTestDataDir + "/bin_json/payload.binpb") require.NoError(t, err) testRunStdoutFile(t, file, 0, convertTestDataDir+"/bin_json/payload.json", "convert", "--type=buf.Foo", convertTestDataDir+"/bin_json/image.binpb", "--to", "-#format=json", ) }) t.Run("stdin-json-payload-to-binpb-with-image", func(t *testing.T) { t.Parallel() file, err := os.Open(convertTestDataDir + "/bin_json/payload.json") require.NoError(t, err) testRunStdoutFile(t, file, 0, convertTestDataDir+"/bin_json/payload.binpb", "convert", "--type=buf.Foo", convertTestDataDir+"/bin_json/image.binpb", "--from", "-#format=json", "--to", "-#format=binpb", ) }) t.Run("stdin-json-payload-to-yaml-with-image", func(t *testing.T) { t.Parallel() file, err := os.Open(convertTestDataDir + "/bin_json/payload.json") require.NoError(t, err) testRunStdoutFile(t, file, 0, convertTestDataDir+"/bin_json/payload.yaml", "convert", "--type=buf.Foo", convertTestDataDir+"/bin_json/image.yaml", "--from", "-#format=json", "--to", "-#format=yaml", ) }) t.Run("stdin-image-json-to-binpb", func(t *testing.T) { t.Parallel() file, err := os.Open(convertTestDataDir + "/bin_json/image.json") require.NoError(t, err) testRunStdoutFile(t, file, 0, convertTestDataDir+"/bin_json/payload.binpb", "convert", "--type=buf.Foo", "-#format=json", "--from="+convertTestDataDir+"/bin_json/payload.json", "--to", "-#format=binpb", ) }) t.Run("stdin-image-json-to-yaml", func(t *testing.T) { t.Parallel() file, err := os.Open(convertTestDataDir + "/bin_json/image.json") require.NoError(t, err) testRunStdoutFile(t, file, 0, convertTestDataDir+"/bin_json/payload.yaml", "convert", "--type=buf.Foo", "-#format=json", "--from="+convertTestDataDir+"/bin_json/payload.json", "--to", "-#format=yaml", ) }) t.Run("stdin-image-txtpb-to-binpb", func(t *testing.T) { t.Parallel() file, err := os.Open(convertTestDataDir + "/bin_json/image.txtpb") require.NoError(t, err) testRunStdoutFile(t, file, 0, convertTestDataDir+"/bin_json/payload.binpb", "convert", "--type=buf.Foo", "-#format=txtpb", "--from="+convertTestDataDir+"/bin_json/payload.txtpb", "--to", "-#format=binpb", ) }) t.Run("stdin-image-yaml-to-binpb", func(t *testing.T) { t.Parallel() file, err := os.Open(convertTestDataDir + "/bin_json/image.yaml") require.NoError(t, err) testRunStdoutFile(t, file, 0, convertTestDataDir+"/bin_json/payload.binpb", "convert", "--type=buf.Foo", "-#format=yaml", "--from="+convertTestDataDir+"/bin_json/payload.yaml", "--to", "-#format=binpb", ) }) } func TestFormat(t *testing.T) { t.Parallel() testRunStdout( t, nil, 0, ` syntax = "proto3"; package simple; message Object { string key = 1; bytes value = 2; } `, "format", filepath.Join("testdata", "format", "simple"), ) } func TestFormatSingleFile(t *testing.T) { t.Parallel() tempDir := t.TempDir() testRunStdout( t, nil, 0, ``, "format", filepath.Join("testdata", "format", "simple"), "-o", filepath.Join(tempDir, "simple.formatted"), ) testRunStdout( t, nil, 0, ``, "format", filepath.Join(tempDir, "simple.formatted"), "-d", ) } func TestFormatDiff(t *testing.T) { t.Parallel() tempDir := t.TempDir() stdout := bytes.NewBuffer(nil) testRun( t, 0, nil, stdout, "format", filepath.Join("testdata", "format", "diff"), "-d", "-o", filepath.Join(tempDir, "formatted"), ) assert.Contains( t, stdout.String(), ` @@ -1,13 +1,7 @@ - syntax = "proto3"; `, ) testRunStdout( t, nil, 0, ``, "format", filepath.Join(tempDir, "formatted"), "-d", ) } // Tests if the exit code is set for common invocations of buf format // with the --exit-code flag. func TestFormatExitCode(t *testing.T) { t.Parallel() stdout := bytes.NewBuffer(nil) testRun( t, bufctl.ExitCodeFileAnnotation, nil, stdout, "format", filepath.Join("testdata", "format", "diff"), "--exit-code", ) assert.NotEmpty(t, stdout.String()) stdout = bytes.NewBuffer(nil) testRun( t, bufctl.ExitCodeFileAnnotation, nil, stdout, "format", filepath.Join("testdata", "format", "diff"), "-d", "--exit-code", ) assert.NotEmpty(t, stdout.String()) } // Tests if the image produced by the formatted result is // equivalent to the original result. func TestFormatEquivalence(t *testing.T) { t.Parallel() tempDir := t.TempDir() testRunStdout( t, nil, 0, ``, "build", filepath.Join("testdata", "format", "complex"), "-o", filepath.Join(tempDir, "image.binpb"), "--exclude-source-info", ) testRunStdout( t, nil, 0, ``, "format", filepath.Join("testdata", "format", "complex"), "-o", filepath.Join(tempDir, "formatted"), ) testRunStdout( t, nil, 0, ``, "build", filepath.Join(tempDir, "formatted"), "-o", filepath.Join(tempDir, "formatted.binpb"), "--exclude-source-info", ) originalImageData, err := os.ReadFile(filepath.Join(tempDir, "image.binpb")) require.NoError(t, err) formattedImageData, err := os.ReadFile(filepath.Join(tempDir, "formatted.binpb")) require.NoError(t, err) require.Equal(t, originalImageData, formattedImageData) } func TestFormatInvalidFlagCombination(t *testing.T) { t.Parallel() tempDir := t.TempDir() testRunStderrContainsNoWarn( t, nil, 1, []string{ `Failure: cannot use --output when using --write`, }, "format", filepath.Join("testdata", "format", "diff"), "-w", "-o", filepath.Join(tempDir, "formatted"), ) } func TestFormatInvalidWriteWithModuleReference(t *testing.T) { t.Parallel() testRunStderrContainsNoWarn( t, nil, 1, []string{ `Failure: invalid input "buf.build/acme/weather" when using --write: must be a directory or proto file`, }, "format", "buf.build/acme/weather", "-w", ) } func TestFormatInvalidIncludePackageFiles(t *testing.T) { t.Parallel() testRunStderrContainsNoWarn( t, nil, 1, []string{ "Failure: cannot specify include_package_files=true with format", }, "format", filepath.Join("testdata", "format", "simple", "simple.proto#include_package_files=true"), ) } func TestFormatInvalidInputDoesNotCreateDirectory(t *testing.T) { t.Parallel() tempDir := t.TempDir() testRunStdoutStderrNoWarn( t, nil, 1, "", filepath.FromSlash(`Failure: testdata/format/invalid/invalid.proto:4:12: syntax error: unexpected '.', expecting '{'`), "format", filepath.Join("testdata", "format", "invalid"), "-o", filepath.Join(tempDir, "formatted", "invalid"), // Directory output. ) _, err := os.Stat(filepath.Join(tempDir, "formatted", "invalid")) assert.True(t, os.IsNotExist(err)) testRunStdoutStderrNoWarn( t, nil, 1, "", filepath.FromSlash(`Failure: testdata/format/invalid/invalid.proto:4:12: syntax error: unexpected '.', expecting '{'`), "format", filepath.Join("testdata", "format", "invalid"), "-o", filepath.Join(tempDir, "formatted", "invalid", "invalid.proto"), // Single file output. ) _, err = os.Stat(filepath.Join(tempDir, "formatted", "invalid")) assert.True(t, os.IsNotExist(err)) } func TestConvertRoundTrip(t *testing.T) { t.Parallel() tempDir := t.TempDir() testRunStdout( t, nil, 0, ``, "build", filepath.Join("testdata", "success"), "-o", filepath.Join(tempDir, "image.binpb"), ) t.Run("stdin and stdout", func(t *testing.T) { t.Parallel() stdin := bytes.NewBufferString(`{"one":"55"}`) encodedMessage := bytes.NewBuffer(nil) decodedMessage := bytes.NewBuffer(nil) testRun( t, 0, stdin, encodedMessage, "convert", filepath.Join(tempDir, "image.binpb"), "--type", "buf.Foo", "--from", "-#format=json", ) testRun( t, 0, encodedMessage, decodedMessage, "convert", filepath.Join(tempDir, "image.binpb"), "--type", "buf.Foo", ) assert.JSONEq(t, `{"one":"55"}`, decodedMessage.String()) }) t.Run("stdin and stdout with type specified", func(t *testing.T) { t.Parallel() stdin := bytes.NewBufferString(`{"one":"55"}`) encodedMessage := bytes.NewBuffer(nil) decodedMessage := bytes.NewBuffer(nil) testRun( t, 0, stdin, encodedMessage, "convert", filepath.Join(tempDir, "image.binpb"), "--type", "buf.Foo", "--from", "-#format=json", "--to", "-#format=binpb", ) testRun( t, 0, encodedMessage, decodedMessage, "convert", filepath.Join(tempDir, "image.binpb"), "--type", "buf.Foo", "--from", "-#format=binpb", "--to", "-#format=json", ) assert.JSONEq(t, `{"one":"55"}`, decodedMessage.String()) }) t.Run("file output and input", func(t *testing.T) { t.Parallel() stdin := bytes.NewBufferString(`{"one":"55"}`) decodedMessage := bytes.NewBuffer(nil) testRun( t, 0, stdin, nil, "convert", filepath.Join(tempDir, "image.binpb"), "--type", "buf.Foo", "--from", "-#format=json", "--to", filepath.Join(tempDir, "decoded_message.binpb"), ) testRun( t, 0, nil, decodedMessage, "convert", filepath.Join(tempDir, "image.binpb"), "--type", "buf.Foo", "--from", filepath.Join(tempDir, "decoded_message.binpb"), ) assert.JSONEq(t, `{"one":"55"}`, decodedMessage.String()) }) } func TestProtoFileNoWorkspaceOrModule(t *testing.T) { t.Parallel() // We can build a simple proto file re that does not belong to any workspace or module // based on the directory of the input. testRunStdout( t, nil, 0, "", "build", filepath.Join("testdata", "protofileref", "noworkspaceormodule", "success", "simple.proto"), ) // However, we should fail if there is any complexity (e.g. an import that cannot be // resolved) since there is no workspace or module config to base this off of. testRunStdoutStderrNoWarn( t, nil, bufctl.ExitCodeFileAnnotation, "", // no stdout filepath.FromSlash(`testdata/protofileref/noworkspaceormodule/fail/import.proto:3:8:import "`)+`google/type/date.proto": file does not exist`, "build", filepath.Join("testdata", "protofileref", "noworkspaceormodule", "fail", "import.proto"), ) } func TestModuleArchiveDir(t *testing.T) { // Archive that defines module at input path t.Parallel() zipDir := createZipFromDir( t, filepath.Join("testdata", "failarchive"), "archive.zip", ) testRunStdout( t, nil, bufctl.ExitCodeFileAnnotation, filepath.FromSlash(`fail/buf/buf.proto:3:1:Files with package "other" must be within a directory "other" relative to root but were in directory "buf". fail/buf/buf.proto:6:9:Field name "oneTwo" should be lower_snake_case, such as "one_two".`), "lint", filepath.Join(zipDir, "archive.zip#subdir=fail"), ) } func TestLintDisabledForModuleInWorkspace(t *testing.T) { t.Parallel() testRunStdout( t, nil, bufctl.ExitCodeFileAnnotation, filepath.FromSlash(`testdata/lint_ignore_disabled/proto/a.proto:3:9:Message name "foo" should be PascalCase, such as "Foo".`), "lint", filepath.Join("testdata", "lint_ignore_disabled"), ) } func TestLintNoSourceCodeInfoIgnores(t *testing.T) { t.Parallel() tempDir := t.TempDir() // Build image without source code info testRunStdout( t, nil, 0, ``, "build", "--exclude-source-info", filepath.Join("testdata", "fail"), "-o", filepath.Join(tempDir, "image.binpb"), ) testRunStdout( t, nil, 0, ``, "lint", filepath.Join(tempDir, "image.binpb"), "--config", `{ "version": "v2", "lint": { "ignore_only": { "FIELD_LOWER_SNAKE_CASE": ["buf/buf.proto"], "PACKAGE_DIRECTORY_MATCH": ["buf/buf.proto"], "PACKAGE_VERSION_SUFFIX": ["buf/buf.proto"], }, }, }`, ) } // testBuildLsFilesFormatImport does effectively an ls-files, but via doing a build of an Image, and then // listing the files from the image as if --format=import was set. func testBuildLsFilesFormatImport(t *testing.T, expectedExitCode int, expectedFiles []string, buildArgs ...string) { buffer := bytes.NewBuffer(nil) testRun(t, expectedExitCode, nil, buffer, append([]string{"build", "-o", "-"}, buildArgs...)...) protoImage := &imagev1.Image{} err := protoencoding.NewWireUnmarshaler(nil).Unmarshal(buffer.Bytes(), protoImage) require.NoError(t, err) image, err := bufimage.NewImageForProto(protoImage) require.NoError(t, err) var paths []string for _, imageFile := range image.Files() { paths = append(paths, imageFile.Path()) } require.Equal(t, expectedFiles, paths) } func testModInit(t *testing.T, expectedData string, document bool, name string, deps ...string) { tempDir := t.TempDir() baseArgs := []string{"mod", "init"} args := append(baseArgs, "-o", tempDir) if document { args = append(args, "--doc") } if name != "" { args = append(args, "--name", name) } testRun(t, 0, nil, nil, args...) data, err := os.ReadFile(filepath.Join(tempDir, "buf.yaml")) require.NoError(t, err) require.Equal(t, expectedData, string(data)) } func testRunStdout(t *testing.T, stdin io.Reader, expectedExitCode int, expectedStdout string, args ...string) { appcmdtesting.Run( t, newRootCommand, appcmdtesting.WithEnv(internaltesting.NewEnvFunc(t)), appcmdtesting.WithStdin(stdin), appcmdtesting.WithExpectedExitCode(expectedExitCode), appcmdtesting.WithExpectedStdout(expectedStdout), appcmdtesting.WithArgs(args...), ) } func testRunStderr(t *testing.T, stdin io.Reader, expectedExitCode int, expectedStderr string, args ...string) { appcmdtesting.Run( t, newRootCommand, appcmdtesting.WithEnv(internaltesting.NewEnvFunc(t)), appcmdtesting.WithStdin(stdin), appcmdtesting.WithExpectedExitCode(expectedExitCode), appcmdtesting.WithExpectedStderr(expectedStderr), appcmdtesting.WithArgs(args...), ) } func testRunStdoutStderrNoWarn(t *testing.T, stdin io.Reader, expectedExitCode int, expectedStdout string, expectedStderr string, args ...string) { appcmdtesting.Run( t, newRootCommand, appcmdtesting.WithEnv(internaltesting.NewEnvFunc(t)), appcmdtesting.WithStdin(stdin), appcmdtesting.WithExpectedExitCode(expectedExitCode), appcmdtesting.WithExpectedStdout(expectedStdout), appcmdtesting.WithExpectedStderr(expectedStderr), appcmdtesting.WithArgs( // we do not want warnings to be part of our stderr test calculation append( args, "--no-warn", )..., ), ) } func testRunStderrContainsNoWarn(t *testing.T, stdin io.Reader, expectedExitCode int, expectedStderrPartials []string, args ...string) { appcmdtesting.Run( t, newRootCommand, appcmdtesting.WithEnv(internaltesting.NewEnvFunc(t)), appcmdtesting.WithStdin(stdin), appcmdtesting.WithExpectedExitCode(expectedExitCode), appcmdtesting.WithExpectedStderrPartials(expectedStderrPartials...), appcmdtesting.WithArgs( // we do not want warnings to be part of our stderr test calculation append( args, "--no-warn", )..., ), ) } func testRunStdoutFile(t *testing.T, stdin io.Reader, expectedExitCode int, wantFile string, args ...string) { wantReader, err := os.Open(wantFile) require.NoError(t, err) wantBytes, err := io.ReadAll(wantReader) require.NoError(t, err) testRunStdout( t, stdin, expectedExitCode, string(wantBytes), args..., ) } func testRun( t *testing.T, expectedExitCode int, stdin io.Reader, stdout io.Writer, args ...string, ) { appcmdtesting.Run( t, newRootCommand, appcmdtesting.WithEnv(internaltesting.NewEnvFunc(t)), appcmdtesting.WithStdin(stdin), appcmdtesting.WithStdout(stdout), appcmdtesting.WithExpectedExitCode(expectedExitCode), appcmdtesting.WithArgs(args...), ) } func getRuleIDsFromLsBreaking(t *testing.T, fileVersion string, useIDs []string, exceptIDs []string) []string { t.Helper() stdout := bytes.NewBuffer(nil) appcmdtesting.Run( t, newRootCommand, appcmdtesting.WithEnv(internaltesting.NewEnvFunc(t)), appcmdtesting.WithStdout(stdout), appcmdtesting.WithExpectedExitCode(0), appcmdtesting.WithArgs( "config", "ls-breaking-rules", "--format=json", "--configured-only", "--config", fmt.Sprintf( `{ "version": %q, "breaking": { "use": %s, "except": %s } }`, fileVersion, "["+strings.Join(xslices.Map(useIDs, func(s string) string { return strconv.Quote(s) }), ",")+"]", "["+strings.Join(xslices.Map(exceptIDs, func(s string) string { return strconv.Quote(s) }), ",")+"]", ), ), ) var ids []string decoder := json.NewDecoder(stdout) type entry struct { ID string } for { var entry entry err := decoder.Decode(&entry) if errors.Is(err, io.EOF) { break } require.NoError(t, err) ids = append(ids, entry.ID) } sort.Strings(ids) return ids } func testLsRuleOutputJSON( t *testing.T, ruleType check.RuleType, config string, expectedRules []*outputCheckRule, ) { stdout := bytes.NewBuffer(nil) stderr := bytes.NewBuffer(nil) var command string switch ruleType { case check.RuleTypeLint: command = "ls-lint-rules" case check.RuleTypeBreaking: command = "ls-breaking-rules" default: t.Errorf("invalid rule type %v", ruleType) t.FailNow() } appcmdtesting.Run( t, newRootCommand, appcmdtesting.WithEnv(internaltesting.NewEnvFunc(t)), appcmdtesting.WithStdout(stdout), appcmdtesting.WithStderr(stderr), appcmdtesting.WithExpectedExitCode(0), appcmdtesting.WithArgs( "config", command, "--configured-only", "--config", config, "--format", "json", ), ) outputRules := xslices.Map( xslices.Filter( bytes.Split(stdout.Bytes(), []byte("\n")), func(outputBytes []byte) bool { return len(outputBytes) > 0 }, ), func(outputBytes []byte) *outputCheckRule { var outputRule outputCheckRule require.NoError(t, json.Unmarshal(outputBytes, &outputRule), "unable to unmarshal %s", string(outputBytes)) return &outputRule }, ) require.Equal(t, expectedRules, outputRules) } ================================================ FILE: cmd/buf/buf_unix_test.go ================================================ // Copyright 2020-2026 Buf Technologies, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. //go:build aix || darwin || dragonfly || freebsd || (js && wasm) || linux || netbsd || openbsd || solaris package main import ( "path/filepath" "testing" ) func TestLsFilesSymlinks(t *testing.T) { t.Parallel() testRunStdout( t, nil, 0, // b.proto links to a .go file, which shouldn't be parsed. // This requires ls-files to not build an Image if we don't include imports // on ls-files. This test effectively verifies that we don't build unless // we have to. filepath.FromSlash(`testdata/symlinks/a.proto testdata/symlinks/b.proto`), "ls-files", filepath.Join("testdata", "symlinks"), ) testRunStdout( t, nil, 0, filepath.FromSlash(`testdata/symlinks/a.proto`), "ls-files", "--disable-symlinks", filepath.Join("testdata", "symlinks"), ) } func TestBuildSymlinks(t *testing.T) { t.Parallel() testRunStdout( t, nil, 100, ``, "build", filepath.Join("testdata", "symlinks"), ) testRunStdout( t, nil, 0, ``, "build", "--disable-symlinks", filepath.Join("testdata", "symlinks"), ) } ================================================ FILE: cmd/buf/imports_test.go ================================================ // Copyright 2020-2026 Buf Technologies, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package main import ( "io" "path/filepath" "strings" "testing" "buf.build/go/app/appcmd" "buf.build/go/app/appcmd/appcmdtesting" "github.com/bufbuild/buf/private/buf/bufctl" "github.com/bufbuild/buf/private/bufpkg/bufmodule" "github.com/bufbuild/buf/private/bufpkg/bufparse" "github.com/bufbuild/buf/private/pkg/uuidutil" "github.com/stretchr/testify/require" ) func TestValidNoImports(t *testing.T) { t.Parallel() testRunStderrWithCache( t, nil, 0, "", "build", filepath.Join("testdata", "imports", "success", "people"), ) } func TestValidImportFromCache(t *testing.T) { t.Parallel() testRunStderrWithCache( t, nil, 0, "", "build", filepath.Join("testdata", "imports", "success", "students"), ) } func TestValidImportFromCorruptedCacheFile(t *testing.T) { t.Parallel() moduleFullName, err := bufparse.NewFullName("bufbuild.test", "bufbot", "people") require.NoError(t, err) commitID, err := uuidutil.FromDashless("fc7d540124fd42db92511c19a60a1d98") require.NoError(t, err) expectedDigest, err := bufmodule.ParseDigest("b5:b22338d6faf2a727613841d760c9cbfd21af6950621a589df329e1fe6611125904c39e22a73e0aa8834006a514dbd084e6c33b6bef29c8e4835b4b9dec631465") require.NoError(t, err) actualDigest, err := bufmodule.ParseDigest("b5:87403abcc5ec8403180536840a46bef8751df78caa8ad4b46939f4673d8bd58663d0f593668651bb2cd23049fedac4989e8b28c7e0e36b9b524f58ab09bf1053") require.NoError(t, err) digestMismatchError := &bufmodule.DigestMismatchError{ FullName: moduleFullName, CommitID: commitID, ExpectedDigest: expectedDigest, ActualDigest: actualDigest, } appcmdtesting.Run( t, func(use string) *appcmd.Command { return newRootCommand(use) }, appcmdtesting.WithExpectedExitCode(1), appcmdtesting.WithExpectedStderr(appFailureError(digestMismatchError).Error()), appcmdtesting.WithEnv( func(use string) map[string]string { return map[string]string{ useEnvVar(use, "CACHE_DIR"): filepath.Join("testdata", "imports", "corrupted_cache_file"), } }, ), appcmdtesting.WithArgs( "build", filepath.Join("testdata", "imports", "success", "students"), "--no-warn", ), ) } func TestValidImportFromCorruptedCacheDep(t *testing.T) { t.Parallel() moduleFullName, err := bufparse.NewFullName("bufbuild.test", "bufbot", "students") require.NoError(t, err) commitID, err := uuidutil.FromDashless("6c776ed5bee54462b06d31fb7f7c16b8") require.NoError(t, err) expectedDigest, err := bufmodule.ParseDigest("b5:01764dd31d0e1b8355eb3b262bba4539657af44872df6e4dfec76f57fbd9f1ae645c7c9c607db5c8352fb7041ca97111e3b0f142dafc1028832acbbc14ba1d70") require.NoError(t, err) actualDigest, err := bufmodule.ParseDigest("b5:975dad3641303843fb6a06eedf038b0e6ff41da82b8a483920afb36011e0b0a24f720a2407f5e0783389530486ff410b7e132f219add69a5c7324d54f6f89a6c") require.NoError(t, err) digestMismatchError := &bufmodule.DigestMismatchError{ FullName: moduleFullName, CommitID: commitID, ExpectedDigest: expectedDigest, ActualDigest: actualDigest, } appcmdtesting.Run( t, func(use string) *appcmd.Command { return newRootCommand(use) }, appcmdtesting.WithExpectedExitCode(1), appcmdtesting.WithExpectedStderr(appFailureError(digestMismatchError).Error()), appcmdtesting.WithEnv( func(use string) map[string]string { return map[string]string{ useEnvVar(use, "CACHE_DIR"): filepath.Join("testdata", "imports", "corrupted_cache_dep"), } }, ), appcmdtesting.WithArgs( "build", filepath.Join("testdata", "imports", "success", "school"), "--no-warn", ), ) } func TestValidImportTransitiveFromCache(t *testing.T) { t.Parallel() testRunStderrWithCache( t, nil, 0, "", "build", filepath.Join("testdata", "imports", "success", "school"), ) } func TestValidImportWKT(t *testing.T) { t.Parallel() testRunStderr( t, nil, 0, "", // no warnings "build", filepath.Join("testdata", "imports", "success", "wkt"), ) } func TestInvalidNonexistentImport(t *testing.T) { t.Parallel() testRunStderrWithCache( t, nil, bufctl.ExitCodeFileAnnotation, filepath.FromSlash(`testdata/imports/failure/people/people/v1/people1.proto:5:8:import "nonexistent.proto": file does not exist`), "build", filepath.Join("testdata", "imports", "failure", "people"), ) } func TestInvalidNonexistentImportFromDirectDep(t *testing.T) { t.Parallel() testRunStderrWithCache( t, nil, bufctl.ExitCodeFileAnnotation, filepath.FromSlash(`testdata/imports/failure/students/students/v1/students.proto:`)+`6:8:import "people/v1/people_nonexistent.proto": file does not exist`, "build", filepath.Join("testdata", "imports", "failure", "students"), ) } func TestInvalidImportFromTransitive(t *testing.T) { t.Parallel() // We actually want to verify that there are no warnings now. Transitive dependencies not declared // in your buf.yaml are acceptable now. testRunStderrContainsWithCache( t, nil, 0, []string{ `WARN`, `File "school/v1/school1.proto" imports "people/v1/people1.proto", which is not in your workspace or in the dependencies declared in your buf.yaml`, `File "school/v1/school1.proto" imports "people/v1/people2.proto", which is not in your workspace or in the dependencies declared in your buf.yaml`, }, "build", filepath.Join("testdata", "imports", "failure", "school"), ) } func TestInvalidImportFromTransitiveWorkspace(t *testing.T) { t.Parallel() testRunStderrWithCache( t, nil, 0, // We actually want to verify that there are no warnings now. deps in your v1 buf.yaml may actually // have an effect - they can affect your buf.lock. "", "build", filepath.Join("testdata", "imports", "failure", "workspace", "transitive_imports"), ) } func TestValidImportFromLocalOnlyWorkspaceUnnamedModules(t *testing.T) { t.Parallel() testRunStderr( t, nil, 0, "", // no warnings "build", filepath.Join("testdata", "imports", "success", "workspace", "unnamed_local_only_modules"), ) } func TestGraphNoWarningsValidImportFromWorkspaceNamedModules(t *testing.T) { t.Parallel() testRunStderr( t, nil, 0, "", // no warnings "dep", "graph", filepath.Join("testdata", "imports", "success", "workspace", "valid_explicit_deps"), ) } func testRunStderrWithCache(t *testing.T, stdin io.Reader, expectedExitCode int, expectedStderr string, args ...string) { appcmdtesting.Run( t, func(use string) *appcmd.Command { return newRootCommand(use) }, appcmdtesting.WithExpectedExitCode(expectedExitCode), appcmdtesting.WithExpectedStderr(expectedStderr), appcmdtesting.WithEnv( func(use string) map[string]string { return map[string]string{ useEnvVar(use, "CACHE_DIR"): filepath.Join("testdata", "imports", "cache"), } }, ), appcmdtesting.WithStdin(stdin), appcmdtesting.WithArgs(args...), ) } func testRunStderrContainsWithCache(t *testing.T, stdin io.Reader, expectedExitCode int, expectedStderrPartials []string, args ...string) { appcmdtesting.Run( t, func(use string) *appcmd.Command { return newRootCommand(use) }, appcmdtesting.WithExpectedExitCode(expectedExitCode), appcmdtesting.WithExpectedStderrPartials(expectedStderrPartials...), appcmdtesting.WithEnv( func(use string) map[string]string { return map[string]string{ useEnvVar(use, "CACHE_DIR"): filepath.Join("testdata", "imports", "cache"), } }, ), appcmdtesting.WithStdin(stdin), appcmdtesting.WithArgs(args...), ) } func useEnvVar(use string, suffix string) string { return strings.ToUpper(use) + "_" + suffix } ================================================ FILE: cmd/buf/internal/command/alpha/protoc/const_unix.go ================================================ // Copyright 2020-2026 Buf Technologies, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Matching the unix-like build tags in the Golang source i.e. https://github.com/golang/go/blob/912f0750472dd4f674b69ca1616bfaf377af1805/src/os/file_unix.go#L6 //go:build aix || darwin || dragonfly || freebsd || (js && wasm) || linux || netbsd || openbsd || solaris package protoc // https://github.com/protocolbuffers/protobuf/blob/336ed1820a4f2649c9aa3953d5059b03b7a77100/src/google/protobuf/compiler/command_line_interface.cc#L892-L896 // // This will be ":" for all unix-like platforms including darwin. // This will be ";" for windows. const includeDirPathSeparator = ":" ================================================ FILE: cmd/buf/internal/command/alpha/protoc/const_windows.go ================================================ // Copyright 2020-2026 Buf Technologies, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. //go:build windows package protoc // https://github.com/protocolbuffers/protobuf/blob/336ed1820a4f2649c9aa3953d5059b03b7a77100/src/google/protobuf/compiler/command_line_interface.cc#L892-L896 // // This will be ":" for all unix-like platforms including darwin. // This will be ";" for windows. const includeDirPathSeparator = ";" ================================================ FILE: cmd/buf/internal/command/alpha/protoc/errors.go ================================================ // Copyright 2020-2026 Buf Technologies, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package protoc import ( "errors" "fmt" ) var ( errNoInputFiles = errors.New("no input files specified") errArgEmpty = errors.New("empty argument specified") ) func newCannotSpecifyOptWithoutOutError(pluginName string) error { return fmt.Errorf("cannot specify --%s_opt without --%s_out", pluginName, pluginName) } func newCannotSpecifyPathWithoutOutError(pluginName string) error { return fmt.Errorf("cannot specify --%s=protoc-gen-%s without --%s_out", pluginPathValuesFlagName, pluginName, pluginName) } func newRecursiveReferenceError(flagFilePath string) error { return fmt.Errorf("%s recursively referenced", flagFilePath) } func newDuplicateOutError(pluginName string) error { return fmt.Errorf("duplicate --%s_out", pluginName) } func newEmptyOptError(pluginName string) error { return fmt.Errorf("empty option value for %s", pluginName) } func newPluginPathValueEmptyError() error { return fmt.Errorf("--%s had an empty value", pluginPathValuesFlagName) } func newPluginPathValueInvalidError(pluginPathValue string) error { return fmt.Errorf("--%s value invalid: %s", pluginPathValuesFlagName, pluginPathValue) } func newPluginPathNameInvalidPrefixError(pluginName string) error { return fmt.Errorf(`--%s had name %q which must be prefixed by "protoc-gen-"`, pluginPathValuesFlagName, pluginName) } func newDuplicatePluginPathError(pluginName string) error { return fmt.Errorf("duplicate --%s for protoc-gen-%s", pluginPathValuesFlagName, pluginName) } func newEncodeNotSupportedError() error { return fmt.Errorf( `--%s is not supported by buf. Buf only handles the binary and JSON formats for now, however we can support this flag if there is sufficient demand. Please email us at support@buf.build if this is a need for your organization.`, encodeFlagName, ) } func newDecodeNotSupportedError() error { return fmt.Errorf( `--%s is not supported by buf. Buf only handles the binary and JSON formats for now, however we can support this flag if there is sufficient demand. Please email us at support@buf.build if this is a need for your organization.`, decodeFlagName, ) } func newDecodeRawNotSupportedError() error { return fmt.Errorf( `--%s is not supported by buf. Buf only handles the binary and JSON formats for now, however we can support this flag if there is sufficient demand. Please email us at support@buf.build if this is a need for your organization.`, decodeRawFlagName, ) } func newDescriptorSetInNotSupportedError() error { return fmt.Errorf( `--%s is not supported by buf. Buf will work with cross-repository imports Buf Schema Registry, which will be based on source files, not pre-built Images. We think this is a much safer option that leads to less errors and more consistent results. Please email us at support@buf.build if this is a need for your organization.`, descriptorSetInFlagName, ) } ================================================ FILE: cmd/buf/internal/command/alpha/protoc/flags.go ================================================ // Copyright 2020-2026 Buf Technologies, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package protoc import ( "fmt" "os" "path/filepath" "sort" "strings" "buf.build/go/standard/xstrings" "github.com/bufbuild/buf/private/buf/buffetch" "github.com/bufbuild/buf/private/bufpkg/bufanalysis" "github.com/bufbuild/buf/private/pkg/normalpath" "github.com/spf13/pflag" ) const ( includeDirPathsFlagName = "proto_path" includeImportsFlagName = "include_imports" includeSourceInfoFlagName = "include_source_info" printFreeFieldNumbersFlagName = "print_free_field_numbers" outputFlagName = "descriptor_set_out" pluginPathValuesFlagName = "plugin" errorFormatFlagName = "error_format" byDirFlagName = "by-dir" pluginFakeFlagName = "protoc_plugin_fake" encodeFlagName = "encode" decodeFlagName = "decode" decodeRawFlagName = "decode_raw" descriptorSetInFlagName = "descriptor_set_in" ) var ( defaultIncludeDirPaths = []string{"."} defaultErrorFormat = "gcc" ) type flags struct { IncludeDirPaths []string IncludeImports bool IncludeSourceInfo bool PrintFreeFieldNumbers bool Output string ErrorFormat string ByDir bool } type env struct { flags PluginNamesSortedByOutIndex []string PluginNameToPluginInfo map[string]*pluginInfo FilePaths []string } type flagsBuilder struct { flags PluginPathValues []string Encode string Decode string DecodeRaw bool DescriptorSetIn []string pluginFake []string pluginNameToValue map[string]*pluginValue } func newFlagsBuilder() *flagsBuilder { return &flagsBuilder{ pluginNameToValue: make(map[string]*pluginValue), } } func (f *flagsBuilder) Bind(flagSet *pflag.FlagSet) { flagSet.StringSliceVarP( &f.IncludeDirPaths, includeDirPathsFlagName, "I", // cannot set default due to recursive flag parsing // no way to differentiate between default and set for now // perhaps we could rework pflag usage somehow nil, `The directory paths to include.`, ) flagSet.BoolVar( &f.IncludeImports, includeImportsFlagName, false, `Include imports in the resulting FileDescriptorSet.`, ) flagSet.BoolVar( &f.IncludeSourceInfo, includeSourceInfoFlagName, false, `Include source info in the resulting FileDescriptorSet.`, ) flagSet.BoolVar( &f.PrintFreeFieldNumbers, printFreeFieldNumbersFlagName, false, `Print the free field numbers of all messages.`, ) flagSet.StringVarP( &f.Output, outputFlagName, "o", "", fmt.Sprintf( `The location to write the FileDescriptorSet. Must be one of format %s.`, buffetch.MessageFormatsString, ), ) flagSet.StringVar( &f.ErrorFormat, errorFormatFlagName, // cannot set default due to recursive flag parsing // no way to differentiate between default and set for now // perhaps we could rework pflag usage somehow "", fmt.Sprintf( `The error format to use. Must be one of format %s.`, xstrings.SliceToString(bufanalysis.AllFormatStringsWithAliases), ), ) flagSet.StringSliceVar( &f.PluginPathValues, pluginPathValuesFlagName, nil, `The paths to the plugin executables to use, either in the form "path/to/protoc-gen-foo" or "protoc-gen-foo=path/to/binary".`, ) flagSet.BoolVar( &f.ByDir, byDirFlagName, false, `Execute parallel plugin calls for every directory containing .proto files.`, ) // MUST be a StringArray instead of StringSlice so we do not split on commas // Otherwise --go_out=foo=bar,baz=bat:out would be treated as --go_out=foo=bar --go_out=baz=bat:out flagSet.StringArrayVar( &f.pluginFake, pluginFakeFlagName, nil, `If you are calling this, you should not be.`, ) _ = flagSet.MarkHidden(pluginFakeFlagName) flagSet.StringVar( &f.Encode, encodeFlagName, "", `Not supported by buf.`, ) _ = flagSet.MarkHidden(encodeFlagName) flagSet.StringVar( &f.Decode, decodeFlagName, "", `Not supported by buf.`, ) _ = flagSet.MarkHidden(decodeFlagName) flagSet.BoolVar( &f.DecodeRaw, decodeRawFlagName, false, `Not supported by buf.`, ) _ = flagSet.MarkHidden(decodeRawFlagName) flagSet.StringSliceVar( &f.DescriptorSetIn, descriptorSetInFlagName, nil, `Not supported by buf.`, ) _ = flagSet.MarkHidden(descriptorSetInFlagName) } func (f *flagsBuilder) Normalize(flagSet *pflag.FlagSet, name string) string { if name != "descriptor_set_out" && strings.HasSuffix(name, "_out") { f.pluginFakeParse(name, "_out", true) return pluginFakeFlagName } if strings.HasSuffix(name, "_opt") { f.pluginFakeParse(name, "_opt", false) return pluginFakeFlagName } return strings.ReplaceAll(name, "-", "_") } func (f *flagsBuilder) Build(args []string) (*env, error) { pluginNameToPluginInfo := make(map[string]*pluginInfo) seenFlagFilePaths := make(map[string]struct{}) filePaths, err := f.buildRec(args, pluginNameToPluginInfo, seenFlagFilePaths) if err != nil { return nil, err } if err := f.checkUnsupported(); err != nil { return nil, err } for pluginName, pluginInfo := range pluginNameToPluginInfo { if pluginInfo.Out == "" && len(pluginInfo.Opt) > 0 { return nil, newCannotSpecifyOptWithoutOutError(pluginName) } if pluginInfo.Out == "" && pluginInfo.Path != "" { return nil, newCannotSpecifyPathWithoutOutError(pluginName) } } pluginNamesSortedByOutIndex, err := f.getPluginNamesSortedByOutIndex(pluginNameToPluginInfo) if err != nil { return nil, err } if len(f.IncludeDirPaths) == 0 { f.IncludeDirPaths = defaultIncludeDirPaths } else { f.IncludeDirPaths = splitIncludeDirPaths(f.IncludeDirPaths) } if f.ErrorFormat == "" { f.ErrorFormat = defaultErrorFormat } if len(filePaths) == 0 { return nil, errNoInputFiles } return &env{ flags: f.flags, PluginNamesSortedByOutIndex: pluginNamesSortedByOutIndex, PluginNameToPluginInfo: pluginNameToPluginInfo, FilePaths: filePaths, }, nil } func (f *flagsBuilder) pluginFakeParse(name string, suffix string, isOut bool) { pluginName := strings.TrimSuffix(name, suffix) pluginValue, ok := f.pluginNameToValue[pluginName] if !ok { pluginValue = newPluginValue() f.pluginNameToValue[pluginName] = pluginValue } index := len(f.pluginFake) if isOut { pluginValue.OutIndexes = append(pluginValue.OutIndexes, index) } else { pluginValue.OptIndexes = append(pluginValue.OptIndexes, index) } } func (f *flagsBuilder) buildRec( args []string, pluginNameToPluginInfo map[string]*pluginInfo, seenFlagFilePaths map[string]struct{}, ) ([]string, error) { if err := f.parsePluginNameToPluginInfo(pluginNameToPluginInfo); err != nil { return nil, err } filePaths := make([]string, 0, len(args)) for _, arg := range args { if len(arg) == 0 { return nil, errArgEmpty } if arg[0] != '@' { filePaths = append(filePaths, arg) } else { flagFilePath := normalpath.Unnormalize(arg[1:]) if _, ok := seenFlagFilePaths[flagFilePath]; ok { return nil, newRecursiveReferenceError(flagFilePath) } seenFlagFilePaths[flagFilePath] = struct{}{} data, err := os.ReadFile(flagFilePath) if err != nil { return nil, err } var flagFilePathArgs []string for flagFilePathArg := range strings.SplitSeq(string(data), "\n") { flagFilePathArg = strings.TrimSpace(flagFilePathArg) if flagFilePathArg != "" { flagFilePathArgs = append(flagFilePathArgs, flagFilePathArg) } } subFlagsBuilder := newFlagsBuilder() flagSet := pflag.NewFlagSet(flagFilePath, pflag.ContinueOnError) subFlagsBuilder.Bind(flagSet) flagSet.SetNormalizeFunc(normalizeFunc(subFlagsBuilder.Normalize)) if err := flagSet.Parse(flagFilePathArgs); err != nil { return nil, err } subFilePaths, err := subFlagsBuilder.buildRec( flagSet.Args(), pluginNameToPluginInfo, seenFlagFilePaths, ) if err != nil { return nil, err } if err := f.merge(subFlagsBuilder); err != nil { return nil, err } filePaths = append(filePaths, subFilePaths...) } } return filePaths, nil } // we need to bind a separate flags as pflags overrides the values with defaults if you bind again // note that pflags does not error on duplicates so we do not either func (f *flagsBuilder) merge(subFlagsBuilder *flagsBuilder) error { f.IncludeDirPaths = append(f.IncludeDirPaths, subFlagsBuilder.IncludeDirPaths...) if subFlagsBuilder.IncludeImports { f.IncludeImports = true } if subFlagsBuilder.IncludeSourceInfo { f.IncludeSourceInfo = true } if subFlagsBuilder.PrintFreeFieldNumbers { f.PrintFreeFieldNumbers = true } if subFlagsBuilder.Output != "" { f.Output = subFlagsBuilder.Output } if subFlagsBuilder.ErrorFormat != "" { f.ErrorFormat = subFlagsBuilder.ErrorFormat } if subFlagsBuilder.ByDir { f.ByDir = true } f.PluginPathValues = append(f.PluginPathValues, subFlagsBuilder.PluginPathValues...) if subFlagsBuilder.Encode != "" { f.Encode = subFlagsBuilder.Encode } if subFlagsBuilder.Decode != "" { f.Decode = subFlagsBuilder.Decode } if subFlagsBuilder.DecodeRaw { f.DecodeRaw = true } f.DescriptorSetIn = append(f.DescriptorSetIn, subFlagsBuilder.DescriptorSetIn...) return nil } func (f *flagsBuilder) parsePluginNameToPluginInfo(pluginNameToPluginInfo map[string]*pluginInfo) error { for pluginName, pluginValue := range f.pluginNameToValue { switch len(pluginValue.OutIndexes) { case 0: case 1: out := f.pluginFake[pluginValue.OutIndexes[0]] var opt string if isOutNotAFullPath(out) { split := strings.SplitN(out, ":", 2) switch len(split) { case 1: case 2: out = split[1] opt = split[0] } } pluginInfo, ok := pluginNameToPluginInfo[pluginName] if !ok { pluginInfo = newPluginInfo() pluginNameToPluginInfo[pluginName] = pluginInfo } pluginInfo.Out = out if opt != "" { for value := range strings.SplitSeq(opt, ",") { if value := strings.TrimSpace(value); value != "" { pluginInfo.Opt = append(pluginInfo.Opt, value) } else { return newEmptyOptError(pluginName) } } } default: return newDuplicateOutError(pluginName) } if len(pluginValue.OptIndexes) > 0 { pluginInfo, ok := pluginNameToPluginInfo[pluginName] if !ok { pluginInfo = newPluginInfo() pluginNameToPluginInfo[pluginName] = pluginInfo } for _, optIndex := range pluginValue.OptIndexes { for value := range strings.SplitSeq(f.pluginFake[optIndex], ",") { if value := strings.TrimSpace(value); value != "" { pluginInfo.Opt = append(pluginInfo.Opt, value) } else { return newEmptyOptError(pluginName) } } } } } for _, pluginPathValue := range f.PluginPathValues { var pluginName string var pluginPath string switch split := strings.SplitN(pluginPathValue, "=", 2); len(split) { case 0: return newPluginPathValueEmptyError() case 1: pluginName = filepath.Base(split[0]) pluginPath = split[0] case 2: pluginName = split[0] pluginPath = split[1] default: return newPluginPathValueInvalidError(pluginPathValue) } if !strings.HasPrefix(pluginName, "protoc-gen-") { return newPluginPathNameInvalidPrefixError(pluginName) } pluginName = strings.TrimPrefix(pluginName, "protoc-gen-") pluginInfo, ok := pluginNameToPluginInfo[pluginName] if !ok { pluginInfo = newPluginInfo() pluginNameToPluginInfo[pluginName] = pluginInfo } if pluginInfo.Path != "" { return newDuplicatePluginPathError(pluginName) } pluginInfo.Path = pluginPath } return nil } func (f *flagsBuilder) getPluginNamesSortedByOutIndex( pluginNameToPluginInfo map[string]*pluginInfo, ) ([]string, error) { pluginNames := make([]string, 0, len(pluginNameToPluginInfo)) for pluginName := range pluginNameToPluginInfo { pluginNames = append(pluginNames, pluginName) } var err error sort.Slice( pluginNames, func(i int, j int) bool { pluginName1 := pluginNames[i] pluginName2 := pluginNames[j] pluginValue1, ok := f.pluginNameToValue[pluginName1] if !ok { err = fmt.Errorf("no value for plugin name %q inside pluginNameToValue", pluginName1) return false } pluginValue2, ok := f.pluginNameToValue[pluginName2] if !ok { err = fmt.Errorf("no value for plugin name %q inside pluginNameToValue", pluginName2) return false } if len(pluginValue1.OutIndexes) != 1 { err = fmt.Errorf("%d out indexes for plugin name %q", len(pluginValue1.OutIndexes), pluginName1) return false } if len(pluginValue2.OutIndexes) != 1 { err = fmt.Errorf("%d out indexes for plugin name %q", len(pluginValue2.OutIndexes), pluginName2) return false } return pluginValue1.OutIndexes[0] < pluginValue2.OutIndexes[0] }, ) if err != nil { return nil, err } return pluginNames, nil } func (f *flagsBuilder) checkUnsupported() error { if f.Encode != "" { return newEncodeNotSupportedError() } if f.Decode != "" { return newDecodeNotSupportedError() } if f.DecodeRaw { return newDecodeRawNotSupportedError() } if len(f.DescriptorSetIn) > 0 { return newDescriptorSetInNotSupportedError() } return nil } type pluginValue struct { OutIndexes []int OptIndexes []int } func newPluginValue() *pluginValue { return &pluginValue{} } func normalizeFunc(f func(*pflag.FlagSet, string) string) func(*pflag.FlagSet, string) pflag.NormalizedName { return func(flagSet *pflag.FlagSet, name string) pflag.NormalizedName { return pflag.NormalizedName(f(flagSet, name)) } } // https://github.com/protocolbuffers/protobuf/blob/336ed1820a4f2649c9aa3953d5059b03b7a77100/src/google/protobuf/compiler/command_line_interface.cc#L1699-L1705 // // This roughly supports the equivalent of Java's -classpath flag. // Note that for filenames such as "foo:bar" on unix, this breaks, but our goal is to match // this flag from protoc. func splitIncludeDirPaths(includeDirPaths []string) []string { copyIncludeDirPaths := make([]string, 0, len(includeDirPaths)) for _, includeDirPath := range includeDirPaths { // protocolbuffers/protobuf has true for omit_empty for splitIncludeDirPath := range strings.SplitSeq(includeDirPath, includeDirPathSeparator) { if len(splitIncludeDirPath) > 0 { copyIncludeDirPaths = append(copyIncludeDirPaths, splitIncludeDirPath) } } } return copyIncludeDirPaths } ================================================ FILE: cmd/buf/internal/command/alpha/protoc/flags_test.go ================================================ // Copyright 2020-2026 Buf Technologies, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package protoc import ( "fmt" "path/filepath" "testing" "github.com/spf13/pflag" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestParseFlags(t *testing.T) { t.Parallel() absFilePath, err := filepath.Abs("out") require.NoError(t, err) testCases := []struct { Args []string Expected *env ExpectedError error }{ { ExpectedError: errNoInputFiles, }, { Args: []string{ "foo.proto", }, Expected: &env{ flags: flags{ IncludeDirPaths: defaultIncludeDirPaths, ErrorFormat: defaultErrorFormat, }, FilePaths: []string{ "foo.proto", }, }, }, { Args: []string{ "-I", "proto", "--error_format", "text", "foo.proto", }, Expected: &env{ flags: flags{ IncludeDirPaths: []string{ "proto", }, ErrorFormat: "text", }, FilePaths: []string{ "foo.proto", }, }, }, { Args: []string{ "-I", "proto", "--error_format", "text", "--go_out", "plugins=connect:go_out", "foo.proto", }, Expected: &env{ flags: flags{ IncludeDirPaths: []string{ "proto", }, ErrorFormat: "text", }, PluginNamesSortedByOutIndex: []string{ "go", }, PluginNameToPluginInfo: map[string]*pluginInfo{ "go": { Out: "go_out", Opt: []string{"plugins=connect"}, }, }, FilePaths: []string{ "foo.proto", }, }, }, { Args: []string{ "-I", "proto", "--error_format", "text", "--go_out", "go_out", "--go_opt", "plugins=connect", "foo.proto", }, Expected: &env{ flags: flags{ IncludeDirPaths: []string{ "proto", }, ErrorFormat: "text", }, PluginNamesSortedByOutIndex: []string{ "go", }, PluginNameToPluginInfo: map[string]*pluginInfo{ "go": { Out: "go_out", Opt: []string{"plugins=connect"}, }, }, FilePaths: []string{ "foo.proto", }, }, }, { Args: []string{ "-I", "proto", "--error_format", "text", "--go_out", "go_out", "--go_opt", "plugins=connect", "--plugin", "/bin/protoc-gen-go", "foo.proto", }, Expected: &env{ flags: flags{ IncludeDirPaths: []string{ "proto", }, ErrorFormat: "text", }, PluginNamesSortedByOutIndex: []string{ "go", }, PluginNameToPluginInfo: map[string]*pluginInfo{ "go": { Out: "go_out", Opt: []string{"plugins=connect"}, Path: "/bin/protoc-gen-go", }, }, FilePaths: []string{ "foo.proto", }, }, }, { Args: []string{ "-I", "proto", "--error_format", "text", "--go_out", "go_out", "--go_opt", "plugins=connect", "--plugin", "protoc-gen-go=/bin/foo", "foo.proto", }, Expected: &env{ flags: flags{ IncludeDirPaths: []string{ "proto", }, ErrorFormat: "text", }, PluginNamesSortedByOutIndex: []string{ "go", }, PluginNameToPluginInfo: map[string]*pluginInfo{ "go": { Out: "go_out", Opt: []string{"plugins=connect"}, Path: "/bin/foo", }, }, FilePaths: []string{ "foo.proto", }, }, }, { Args: []string{ "@" + filepath.Join("testdata", "1", "flags.txt"), "foo.proto", }, Expected: &env{ flags: flags{ IncludeDirPaths: []string{ "proto", }, ErrorFormat: "text", }, PluginNamesSortedByOutIndex: []string{ "go", }, PluginNameToPluginInfo: map[string]*pluginInfo{ "go": { Out: "go_out", Opt: []string{"plugins=connect"}, Path: "/bin/protoc-gen-go", }, }, FilePaths: []string{ "foo.proto", }, }, }, { Args: []string{ "@" + filepath.Join("testdata", "2", "flags1.txt"), "foo.proto", }, Expected: &env{ flags: flags{ IncludeDirPaths: []string{ "proto", }, ErrorFormat: "text", }, PluginNamesSortedByOutIndex: []string{ "go", }, PluginNameToPluginInfo: map[string]*pluginInfo{ "go": { Out: "go_out", Opt: []string{"plugins=connect"}, Path: "/bin/protoc-gen-go", }, }, FilePaths: []string{ "foo.proto", }, }, }, { Args: []string{ "@" + filepath.Join("testdata", "3", "flags1.txt"), "foo.proto", }, ExpectedError: newRecursiveReferenceError(filepath.Join("testdata", "3", "flags1.txt")), }, { Args: []string{ "-I", "proto", "--error_format", "text", "--go_out", "plugins=connect:go_out", "--go_opt", "foo=bar", "foo.proto", }, Expected: &env{ flags: flags{ IncludeDirPaths: []string{ "proto", }, ErrorFormat: "text", }, PluginNamesSortedByOutIndex: []string{ "go", }, PluginNameToPluginInfo: map[string]*pluginInfo{ "go": { Out: "go_out", Opt: []string{ "plugins=connect", "foo=bar", }, }, }, FilePaths: []string{ "foo.proto", }, }, }, { Args: []string{ "-I", "proto", "--error_format", "text", "--go_out", "plugins=connect:go_out", "--go_opt", "foo=bar", "--go_opt", "baz=bat", "foo.proto", }, Expected: &env{ flags: flags{ IncludeDirPaths: []string{ "proto", }, ErrorFormat: "text", }, PluginNamesSortedByOutIndex: []string{ "go", }, PluginNameToPluginInfo: map[string]*pluginInfo{ "go": { Out: "go_out", Opt: []string{ "plugins=connect", "foo=bar", "baz=bat", }, }, }, FilePaths: []string{ "foo.proto", }, }, }, { Args: []string{ "-I", "proto", "--error_format", "text", "--go_out", "go_out", "--go_opt", "foo=bar", "--go_opt", "baz=bat", "foo.proto", }, Expected: &env{ flags: flags{ IncludeDirPaths: []string{ "proto", }, ErrorFormat: "text", }, PluginNamesSortedByOutIndex: []string{ "go", }, PluginNameToPluginInfo: map[string]*pluginInfo{ "go": { Out: "go_out", Opt: []string{ "foo=bar", "baz=bat", }, }, }, FilePaths: []string{ "foo.proto", }, }, }, { Args: []string{ "-I", "proto", "--error_format", "text", "--go_out", "foo=bar,baz=bat:go_out", "--go_opt", "one=two,three=four", "--go_opt", "five=six", "foo.proto", }, Expected: &env{ flags: flags{ IncludeDirPaths: []string{ "proto", }, ErrorFormat: "text", }, PluginNamesSortedByOutIndex: []string{ "go", }, PluginNameToPluginInfo: map[string]*pluginInfo{ "go": { Out: "go_out", Opt: []string{ "foo=bar", "baz=bat", "one=two", "three=four", "five=six", }, }, }, FilePaths: []string{ "foo.proto", }, }, }, { Args: []string{ "--go_out", "go_out", "--go_out", "go_out", "foo.proto", }, ExpectedError: newDuplicateOutError("go"), }, { Args: []string{ "--foo_out=foo", "--bar_out=bar", "--baz_out=baz", "foo.proto", }, Expected: &env{ flags: flags{ IncludeDirPaths: defaultIncludeDirPaths, ErrorFormat: defaultErrorFormat, }, PluginNamesSortedByOutIndex: []string{ "foo", "bar", "baz", }, PluginNameToPluginInfo: map[string]*pluginInfo{ "foo": { Out: "foo", }, "bar": { Out: "bar", }, "baz": { Out: "baz", }, }, FilePaths: []string{ "foo.proto", }, }, }, { Args: []string{ "-I", "foo" + includeDirPathSeparator + "bar" + includeDirPathSeparator + "baz", "-I", "bat", "foo.proto", }, Expected: &env{ flags: flags{ IncludeDirPaths: []string{ "foo", "bar", "baz", "bat", }, ErrorFormat: defaultErrorFormat, }, FilePaths: []string{ "foo.proto", }, }, }, { Args: []string{ "-I", "foo" + includeDirPathSeparator + includeDirPathSeparator + "baz", "-I", "bat", "foo.proto", }, Expected: &env{ flags: flags{ IncludeDirPaths: []string{ "foo", "baz", "bat", }, ErrorFormat: defaultErrorFormat, }, FilePaths: []string{ "foo.proto", }, }, }, { Args: []string{ "-I", "foo" + includeDirPathSeparator + "bar" + includeDirPathSeparator, "-I", "bat", "foo.proto", }, Expected: &env{ flags: flags{ IncludeDirPaths: []string{ "foo", "bar", "bat", }, ErrorFormat: defaultErrorFormat, }, FilePaths: []string{ "foo.proto", }, }, }, { Args: []string{ "-I", "proto", "foo.proto", fmt.Sprintf("--go_out=%s", absFilePath), }, Expected: &env{ flags: flags{ IncludeDirPaths: []string{ "proto", }, ErrorFormat: "gcc", }, PluginNamesSortedByOutIndex: []string{ "go", }, PluginNameToPluginInfo: map[string]*pluginInfo{ "go": { Out: absFilePath, }, }, FilePaths: []string{ "foo.proto", }, }, }, { Args: []string{ "-I", "proto", "foo.proto", fmt.Sprintf("--go_out=opt:%s", absFilePath), }, Expected: &env{ flags: flags{ IncludeDirPaths: []string{ "proto", }, ErrorFormat: "gcc", }, PluginNamesSortedByOutIndex: []string{ "go", }, PluginNameToPluginInfo: map[string]*pluginInfo{ "go": { Out: absFilePath, Opt: []string{"opt"}, }, }, FilePaths: []string{ "foo.proto", }, }, }, } for i, testCase := range testCases { name := fmt.Sprintf("%d", i) t.Run(name, func(t *testing.T) { t.Parallel() env, err := testParseFlags(name, testCase.Args) if testCase.ExpectedError != nil { assert.Equal(t, testCase.ExpectedError, err) } else { require.NoError(t, err) if env != nil { // testify counts nil and empty as different // we do not want to have to set empty values in our expected env // so we set them to nil here for comparison if len(env.IncludeDirPaths) == 0 { env.IncludeDirPaths = nil } if len(env.PluginNamesSortedByOutIndex) == 0 { env.PluginNamesSortedByOutIndex = nil } if len(env.PluginNameToPluginInfo) == 0 { env.PluginNameToPluginInfo = nil } if len(env.FilePaths) == 0 { env.FilePaths = nil } } assert.Equal(t, testCase.Expected, env) } }) } } func testParseFlags(name string, args []string) (*env, error) { flagsBuilder := newFlagsBuilder() flagSet := pflag.NewFlagSet(name, pflag.ContinueOnError) flagsBuilder.Bind(flagSet) flagSet.SetNormalizeFunc(normalizeFunc(flagsBuilder.Normalize)) if err := flagSet.Parse(args); err != nil { return nil, err } return flagsBuilder.Build(flagSet.Args()) } ================================================ FILE: cmd/buf/internal/command/alpha/protoc/flags_unix.go ================================================ // Copyright 2020-2026 Buf Technologies, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. //go:build aix || darwin || dragonfly || freebsd || (js && wasm) || linux || netbsd || openbsd || solaris package protoc // isOutNotAFullPath checks if we need to consider the path to be a full path. // // This is always true in unix, and is true if the path is not absolute in windows. // This is needed because i.e.: // // # unix // --go_out=opt:foo/bar // --go_out=foo/bar // # windows // --go_out=opt:C:\foo\bar // --go_out=C:\foo\bar // // protoc uses : in both unix and windows to separate the opt and out, but in windows, // if a full path is given, then we don't want it interpreted as something we should split. func isOutNotAFullPath(path string) bool { return true } ================================================ FILE: cmd/buf/internal/command/alpha/protoc/flags_windows.go ================================================ // Copyright 2020-2026 Buf Technologies, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. //go:build windows package protoc import ( "path/filepath" ) // isOutNotAFullPath checks if we need to consider the path to be a full path. // // This is always true in unix, and is true if the path is not absolute in windows. // This is needed because i.e.: // // # unix // --go_out=opt:foo/bar // --go_out=foo/bar // # windows // --go_out=opt:C:\foo\bar // --go_out=C:\foo\bar // // protoc uses : in both unix and windows to separate the opt and out, but in windows, // if a full path is given, then we don't want it interpreted as something we should split. func isOutNotAFullPath(path string) bool { return !filepath.IsAbs(path) } ================================================ FILE: cmd/buf/internal/command/alpha/protoc/internal/protoc-gen-insertion-point-receiver/main.go ================================================ // Copyright 2020-2026 Buf Technologies, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package main import ( "context" "github.com/bufbuild/protoplugin" "google.golang.org/protobuf/proto" "google.golang.org/protobuf/types/pluginpb" ) func main() { protoplugin.Main(protoplugin.HandlerFunc(handle)) } func handle( _ context.Context, _ protoplugin.PluginEnv, responseWriter protoplugin.ResponseWriter, _ protoplugin.Request, ) error { responseWriter.AddCodeGeneratorResponseFiles( &pluginpb.CodeGeneratorResponse_File{ Name: proto.String("test.txt"), Content: proto.String(` // The following line represents an insertion point named 'example'. // We include a few indentation to verify the whitespace is preserved // in the inserted content. // // @@protoc_insertion_point(example) // // The 'other' insertion point is also included so that we verify // multiple insertion points can be written in a single invocation. // // @@protoc_insertion_point(other) // // Note that all text should be added above the insertion points. `), }, ) return nil } ================================================ FILE: cmd/buf/internal/command/alpha/protoc/internal/protoc-gen-insertion-point-writer/main.go ================================================ // Copyright 2020-2026 Buf Technologies, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package main import ( "context" "github.com/bufbuild/protoplugin" "google.golang.org/protobuf/proto" "google.golang.org/protobuf/types/pluginpb" ) func main() { protoplugin.Main(protoplugin.HandlerFunc(handle)) } func handle( _ context.Context, _ protoplugin.PluginEnv, responseWriter protoplugin.ResponseWriter, _ protoplugin.Request, ) error { responseWriter.AddCodeGeneratorResponseFiles( &pluginpb.CodeGeneratorResponse_File{ Name: proto.String("test.txt"), InsertionPoint: proto.String("example"), Content: proto.String(` // Include this comment on the 'example' insertion point. // This is another example where whitespaces are preserved. // And this demonstrates a newline literal (\n). // And don't forget the windows newline literal (\r\n). `), }, &pluginpb.CodeGeneratorResponse_File{ Name: proto.String("test.txt"), InsertionPoint: proto.String("other"), Content: proto.String(` // Include this comment on the 'other' insertion point. `), }, ) return nil } ================================================ FILE: cmd/buf/internal/command/alpha/protoc/plugin.go ================================================ // Copyright 2020-2026 Buf Technologies, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package protoc import ( "context" "fmt" "log/slog" "strings" "buf.build/go/app" "github.com/bufbuild/buf/private/buf/bufprotopluginexec" "github.com/bufbuild/buf/private/bufpkg/bufimage" "github.com/bufbuild/buf/private/pkg/storage/storageos" "google.golang.org/protobuf/types/pluginpb" ) type pluginInfo struct { // Required Out string // optional Opt []string // optional Path string } func newPluginInfo() *pluginInfo { return &pluginInfo{} } func executePlugin( ctx context.Context, logger *slog.Logger, storageosProvider storageos.Provider, container app.EnvStderrContainer, images []bufimage.Image, pluginName string, pluginInfo *pluginInfo, ) (*pluginpb.CodeGeneratorResponse, error) { generator := bufprotopluginexec.NewGenerator( logger, storageosProvider, ) requests, err := bufimage.ImagesToCodeGeneratorRequests( images, strings.Join(pluginInfo.Opt, ","), bufprotopluginexec.DefaultVersion, false, false, ) if err != nil { return nil, err } var options []bufprotopluginexec.GenerateOption if pluginInfo.Path != "" { options = append(options, bufprotopluginexec.GenerateWithPluginPath(pluginInfo.Path)) } response, err := generator.Generate( ctx, container, pluginName, requests, options..., ) if err != nil { return nil, fmt.Errorf("--%s_out: %v", pluginName, err) } return response, nil } ================================================ FILE: cmd/buf/internal/command/alpha/protoc/protoc.go ================================================ // Copyright 2020-2026 Buf Technologies, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package protoc import ( "context" "errors" "fmt" "log/slog" "strings" "buf.build/go/app" "buf.build/go/app/appcmd" "buf.build/go/app/appext" "buf.build/go/standard/xlog/xslog" "github.com/bufbuild/buf/private/buf/bufcli" "github.com/bufbuild/buf/private/buf/bufctl" "github.com/bufbuild/buf/private/buf/bufprotoc" "github.com/bufbuild/buf/private/buf/bufprotopluginexec" "github.com/bufbuild/buf/private/bufpkg/bufanalysis" "github.com/bufbuild/buf/private/bufpkg/bufimage" "github.com/bufbuild/buf/private/bufpkg/bufimage/bufimageutil" "github.com/bufbuild/buf/private/bufpkg/bufmodule" "github.com/bufbuild/buf/private/bufpkg/bufprotoplugin" "github.com/bufbuild/buf/private/bufpkg/bufprotoplugin/bufprotopluginos" "github.com/bufbuild/buf/private/pkg/storage/storageos" ) // NewCommand returns a new Command. func NewCommand( name string, builder appext.SubCommandBuilder, ) *appcmd.Command { flagsBuilder := newFlagsBuilder() return &appcmd.Command{ Use: name + " ...", Short: "High-performance protoc replacement", Long: `This command replaces protoc using Buf's internal compiler. The implementation is in progress. Although it outperforms mainline protoc, it hasn't yet been optimized. This protoc replacement is currently stable but should be considered a preview. Additional flags: --(.*)_out: Run the named plugin. --(.*)_opt: Options for the named plugin. @filename: Parse arguments from the given filename.`, Run: builder.NewRunFunc( func(ctx context.Context, container appext.Container) error { env, err := flagsBuilder.Build(app.Args(container)) if err != nil { return err } return run(ctx, container, env) }, ), BindFlags: flagsBuilder.Bind, NormalizeFlag: flagsBuilder.Normalize, Version: fmt.Sprintf( "%v.%v-buf", // DefaultVersion has an extra major version that corresponds to // backwards-compatibility level of C++ runtime. The actual version // of the compiler is just the minor and patch versions. bufprotopluginexec.DefaultVersion.GetMinor(), bufprotopluginexec.DefaultVersion.GetPatch(), ), } } func run( ctx context.Context, container appext.Container, env *env, ) (retErr error) { logger := container.Logger() defer xslog.DebugProfile(logger)() if env.PrintFreeFieldNumbers && len(env.PluginNameToPluginInfo) > 0 { return fmt.Errorf("cannot call --%s and plugins at the same time", printFreeFieldNumbersFlagName) } if env.PrintFreeFieldNumbers && env.Output != "" { return fmt.Errorf("cannot call --%s and --%s at the same time", printFreeFieldNumbersFlagName, outputFlagName) } if len(env.PluginNameToPluginInfo) > 0 && env.Output != "" { return fmt.Errorf("cannot call --%s and plugins at the same time", outputFlagName) } logger.DebugContext( ctx, "env", slog.Any("flags", env.flags), slog.Any("plugins", env.PluginNameToPluginInfo), ) storageosProvider := storageos.NewProvider(storageos.ProviderWithSymlinks()) moduleSet, err := bufprotoc.NewModuleSetForProtoc( ctx, logger, storageosProvider, env.IncludeDirPaths, env.FilePaths, ) if err != nil { return err } var buildOptions []bufimage.BuildImageOption // we always need source code info if we are doing generation if len(env.PluginNameToPluginInfo) == 0 && !env.IncludeSourceInfo { buildOptions = append(buildOptions, bufimage.WithExcludeSourceCodeInfo()) } image, err := bufimage.BuildImage( ctx, logger, bufmodule.ModuleSetToModuleReadBucketWithOnlyProtoFiles(moduleSet), buildOptions..., ) if err != nil { var fileAnnotationSet bufanalysis.FileAnnotationSet if errors.As(err, &fileAnnotationSet) { if err := bufanalysis.PrintFileAnnotationSet( container.Stderr(), fileAnnotationSet, env.ErrorFormat, ); err != nil { return err } // we do this even though we're in protoc compatibility mode as we just need to do non-zero // but this also makes us consistent with the rest of buf return bufctl.ErrFileAnnotation } return err } if env.PrintFreeFieldNumbers { fileInfos, err := bufmodule.GetTargetFileInfos( ctx, bufmodule.ModuleSetToModuleReadBucketWithOnlyProtoFiles( moduleSet, ), ) if err != nil { return err } var filePaths []string for _, fileInfo := range fileInfos { filePaths = append(filePaths, fileInfo.Path()) } s, err := bufimageutil.FreeMessageRangeStrings(ctx, filePaths, image) if err != nil { return err } if _, err := container.Stdout().Write([]byte(strings.Join(s, "\n") + "\n")); err != nil { return err } return nil } if len(env.PluginNameToPluginInfo) > 0 { images := []bufimage.Image{image} if env.ByDir { f := func() (retErr error) { defer xslog.DebugProfile(logger)() images, err = bufimage.ImageByDir(image) return err } if err := f(); err != nil { return err } } pluginResponses := make([]*bufprotoplugin.PluginResponse, 0, len(env.PluginNamesSortedByOutIndex)) for _, pluginName := range env.PluginNamesSortedByOutIndex { pluginInfo, ok := env.PluginNameToPluginInfo[pluginName] if !ok { return fmt.Errorf("no value in PluginNamesToPluginInfo for %q", pluginName) } response, err := executePlugin( ctx, logger, storageosProvider, container, images, pluginName, pluginInfo, ) if err != nil { return err } pluginResponses = append(pluginResponses, bufprotoplugin.NewPluginResponse(response, pluginName, pluginInfo.Out)) } if err := bufprotoplugin.ValidatePluginResponses(pluginResponses); err != nil { return err } responseWriter := bufprotopluginos.NewResponseWriter( logger, storageosProvider, ) for _, pluginResponse := range pluginResponses { pluginInfo, ok := env.PluginNameToPluginInfo[pluginResponse.PluginName] if !ok { return fmt.Errorf("no value in PluginNamesToPluginInfo for %q", pluginResponse.PluginName) } if err := responseWriter.AddResponse( ctx, pluginResponse.Response, pluginInfo.Out, ); err != nil { return err } } if err := responseWriter.Close(); err != nil { return err } return nil } if env.Output == "" { return appcmd.NewInvalidArgumentErrorf("required flag %q not set", outputFlagName) } controller, err := bufcli.NewController(container) if err != nil { return err } return controller.PutImage( ctx, env.Output, image, // Actually redundant with bufimage.BuildImageOptions right now. bufctl.WithImageExcludeSourceInfo(!env.IncludeSourceInfo), bufctl.WithImageExcludeImports(!env.IncludeImports), bufctl.WithImageAsFileDescriptorSet(true), ) } ================================================ FILE: cmd/buf/internal/command/alpha/protoc/protoc_test.go ================================================ // Copyright 2020-2026 Buf Technologies, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package protoc import ( "bytes" "fmt" "os" "path/filepath" "testing" "buf.build/go/app" "buf.build/go/app/appcmd" "buf.build/go/app/appcmd/appcmdtesting" "buf.build/go/app/appext" "buf.build/go/standard/xtesting" "github.com/bufbuild/buf/private/buf/buftesting" "github.com/bufbuild/buf/private/pkg/protoencoding" "github.com/bufbuild/buf/private/pkg/prototesting" "github.com/bufbuild/buf/private/pkg/storage" "github.com/bufbuild/buf/private/pkg/storage/storagearchive" "github.com/bufbuild/buf/private/pkg/storage/storagemem" "github.com/bufbuild/buf/private/pkg/storage/storageos" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "google.golang.org/protobuf/types/descriptorpb" ) var buftestingDirPath = filepath.Join( "..", "..", "..", "..", "..", "..", "..", "private", "buf", "buftesting", ) type testPluginInfo struct { name string opt string } func TestOverlap(t *testing.T) { t.Parallel() // https://github.com/bufbuild/buf/issues/113 appcmdtesting.Run( t, func(name string) *appcmd.Command { return NewCommand( name, appext.NewBuilder(name), ) }, appcmdtesting.WithExpectedExitCode(0), appcmdtesting.WithArgs( "-I", filepath.Join("testdata", "overlap", "a"), "-I", filepath.Join("testdata", "overlap", "b"), "-o", app.DevNullFilePath, filepath.Join("testdata", "overlap", "a", "1.proto"), filepath.Join("testdata", "overlap", "b", "2.proto"), ), ) } func TestComparePrintFreeFieldNumbersGoogleapis(t *testing.T) { t.Parallel() googleapisDirPath := buftesting.GetGoogleapisDirPath(t, buftestingDirPath) filePaths := buftesting.GetProtocFilePaths(t, googleapisDirPath, 100) actualProtocStdout := bytes.NewBuffer(nil) buftesting.RunActualProtoc( t, false, false, googleapisDirPath, filePaths, nil, actualProtocStdout, fmt.Sprintf("--%s", printFreeFieldNumbersFlagName), ) appcmdtesting.Run( t, func(name string) *appcmd.Command { return NewCommand( name, appext.NewBuilder(name), ) }, appcmdtesting.WithExpectedExitCode(0), appcmdtesting.WithExpectedStdout(actualProtocStdout.String()), appcmdtesting.WithArgs( append( []string{ "-I", googleapisDirPath, fmt.Sprintf("--%s", printFreeFieldNumbersFlagName), }, filePaths..., )..., ), ) } func TestCompareOutputGoogleapis(t *testing.T) { xtesting.SkipIfShort(t) t.Parallel() googleapisDirPath := buftesting.GetGoogleapisDirPath(t, buftestingDirPath) filePaths := buftesting.GetProtocFilePaths(t, googleapisDirPath, 100) actualProtocFileDescriptorSet := buftesting.GetActualProtocFileDescriptorSet( t, false, false, googleapisDirPath, filePaths, ) bufProtocFileDescriptorSet := testGetBufProtocFileDescriptorSet(t, googleapisDirPath) prototesting.AssertFileDescriptorSetsEqual(t, bufProtocFileDescriptorSet, actualProtocFileDescriptorSet) } func TestCompareGeneratedStubsGoogleapisGo(t *testing.T) { xtesting.SkipIfShort(t) t.Parallel() googleapisDirPath := buftesting.GetGoogleapisDirPath(t, buftestingDirPath) testCompareGeneratedStubs( t, googleapisDirPath, []testPluginInfo{ {name: "go", opt: "Mgoogle/api/auth.proto=foo"}, }, ) } func TestCompareGeneratedStubsGoogleapisGoZip(t *testing.T) { xtesting.SkipIfShort(t) t.Parallel() googleapisDirPath := buftesting.GetGoogleapisDirPath(t, buftestingDirPath) testCompareGeneratedStubsArchive( t, googleapisDirPath, []testPluginInfo{ {name: "go", opt: "Mgoogle/api/auth.proto=foo"}, }, false, ) } func TestCompareGeneratedStubsGoogleapisGoJar(t *testing.T) { xtesting.SkipIfShort(t) t.Parallel() googleapisDirPath := buftesting.GetGoogleapisDirPath(t, buftestingDirPath) testCompareGeneratedStubsArchive( t, googleapisDirPath, []testPluginInfo{ {name: "go", opt: "Mgoogle/api/auth.proto=foo"}, }, true, ) } func TestCompareGeneratedStubsGoogleapisObjc(t *testing.T) { xtesting.SkipIfShort(t) t.Parallel() googleapisDirPath := buftesting.GetGoogleapisDirPath(t, buftestingDirPath) testCompareGeneratedStubs( t, googleapisDirPath, []testPluginInfo{{name: "objc"}}, ) } func TestCompareInsertionPointOutput(t *testing.T) { xtesting.SkipIfShort(t) t.Parallel() insertionTestdataDirPath := filepath.Join("testdata", "insertion") testCompareGeneratedStubs( t, insertionTestdataDirPath, []testPluginInfo{ {name: "insertion-point-receiver"}, {name: "insertion-point-writer"}, }, ) } func TestInsertionPointMixedPathsSuccess(t *testing.T) { xtesting.SkipIfShort(t) t.Parallel() wd, err := os.Getwd() require.NoError(t, err) testInsertionPointMixedPathsSuccess(t, ".", wd) testInsertionPointMixedPathsSuccess(t, wd, ".") } // testInsertionPointMixedPathsSuccess demonstrates that insertion points are able // to generate to the same output directory, even if the absolute path points to // the same place. func testInsertionPointMixedPathsSuccess(t *testing.T, receiverOut string, writerOut string) { dirPath := filepath.Join("testdata", "insertion") filePaths := buftesting.GetProtocFilePaths(t, dirPath, 100) protocFlags := []string{ fmt.Sprintf("--%s_out=%s", "insertion-point-receiver", receiverOut), fmt.Sprintf("--%s_out=%s", "insertion-point-writer", writerOut), } err := prototesting.RunProtoc( t.Context(), []string{dirPath}, filePaths, false, false, map[string]string{ "PATH": os.Getenv("PATH"), }, nil, protocFlags..., ) require.Error(t, err) appcmdtesting.Run( t, func(name string) *appcmd.Command { return NewCommand( name, appext.NewBuilder(name), ) }, appcmdtesting.WithEnv( func(string) map[string]string { return map[string]string{ "PATH": os.Getenv("PATH"), } }, ), appcmdtesting.WithExpectedExitCode(0), appcmdtesting.WithArgs( append( append( protocFlags, "-I", dirPath, "--by-dir", ), filePaths..., )..., ), ) } func testCompareGeneratedStubs( t *testing.T, dirPath string, plugins []testPluginInfo, ) { filePaths := buftesting.GetProtocFilePaths(t, dirPath, 100) actualProtocDir := t.TempDir() bufProtocDir := t.TempDir() var actualProtocPluginFlags []string for _, plugin := range plugins { actualProtocPluginFlags = append(actualProtocPluginFlags, fmt.Sprintf("--%s_out=%s", plugin.name, actualProtocDir)) if plugin.opt != "" { actualProtocPluginFlags = append(actualProtocPluginFlags, fmt.Sprintf("--%s_opt=%s", plugin.name, plugin.opt)) } } buftesting.RunActualProtoc( t, false, false, dirPath, filePaths, map[string]string{ "PATH": os.Getenv("PATH"), }, nil, actualProtocPluginFlags..., ) var bufProtocPluginFlags []string for _, plugin := range plugins { bufProtocPluginFlags = append(bufProtocPluginFlags, fmt.Sprintf("--%s_out=%s", plugin.name, bufProtocDir)) if plugin.opt != "" { bufProtocPluginFlags = append(bufProtocPluginFlags, fmt.Sprintf("--%s_opt=%s", plugin.name, plugin.opt)) } } appcmdtesting.Run( t, func(name string) *appcmd.Command { return NewCommand( name, appext.NewBuilder(name), ) }, appcmdtesting.WithEnv( func(string) map[string]string { return map[string]string{ "PATH": os.Getenv("PATH"), } }, ), appcmdtesting.WithExpectedExitCode(0), appcmdtesting.WithArgs( append( append( bufProtocPluginFlags, "-I", dirPath, "--by-dir", ), filePaths..., )..., ), ) storageosProvider := storageos.NewProvider(storageos.ProviderWithSymlinks()) actualReadWriteBucket, err := storageosProvider.NewReadWriteBucket( actualProtocDir, storageos.ReadWriteBucketWithSymlinksIfSupported(), ) require.NoError(t, err) bufReadWriteBucket, err := storageosProvider.NewReadWriteBucket( bufProtocDir, storageos.ReadWriteBucketWithSymlinksIfSupported(), ) require.NoError(t, err) diff, err := storage.DiffBytes( t.Context(), actualReadWriteBucket, bufReadWriteBucket, ) require.NoError(t, err) assert.Empty(t, string(diff)) } func testCompareGeneratedStubsArchive( t *testing.T, dirPath string, plugins []testPluginInfo, useJar bool, ) { fileExt := ".zip" if useJar { fileExt = ".jar" } filePaths := buftesting.GetProtocFilePaths(t, dirPath, 100) tempDir := t.TempDir() actualProtocFile := filepath.Join(tempDir, "actual-protoc"+fileExt) bufProtocFile := filepath.Join(tempDir, "buf-protoc"+fileExt) var actualProtocPluginFlags []string for _, plugin := range plugins { actualProtocPluginFlags = append(actualProtocPluginFlags, fmt.Sprintf("--%s_out=%s", plugin.name, actualProtocFile)) if plugin.opt != "" { actualProtocPluginFlags = append(actualProtocPluginFlags, fmt.Sprintf("--%s_opt=%s", plugin.name, plugin.opt)) } } buftesting.RunActualProtoc( t, false, false, dirPath, filePaths, map[string]string{ "PATH": os.Getenv("PATH"), }, nil, actualProtocPluginFlags..., ) var bufProtocPluginFlags []string for _, plugin := range plugins { bufProtocPluginFlags = append(bufProtocPluginFlags, fmt.Sprintf("--%s_out=%s", plugin.name, bufProtocFile)) if plugin.opt != "" { bufProtocPluginFlags = append(bufProtocPluginFlags, fmt.Sprintf("--%s_opt=%s", plugin.name, plugin.opt)) } } appcmdtesting.Run( t, func(name string) *appcmd.Command { return NewCommand( name, appext.NewBuilder(name), ) }, appcmdtesting.WithEnv( func(string) map[string]string { return map[string]string{ "PATH": os.Getenv("PATH"), } }, ), appcmdtesting.WithExpectedExitCode(0), appcmdtesting.WithArgs( append( append( bufProtocPluginFlags, "-I", dirPath, "--by-dir", ), filePaths..., )..., ), ) actualData, err := os.ReadFile(actualProtocFile) require.NoError(t, err) actualReadWriteBucket := storagemem.NewReadWriteBucket() err = storagearchive.Unzip( t.Context(), bytes.NewReader(actualData), int64(len(actualData)), actualReadWriteBucket, ) require.NoError(t, err) bufData, err := os.ReadFile(bufProtocFile) require.NoError(t, err) bufReadWriteBucket := storagemem.NewReadWriteBucket() err = storagearchive.Unzip( t.Context(), bytes.NewReader(bufData), int64(len(bufData)), bufReadWriteBucket, ) require.NoError(t, err) diff, err := storage.DiffBytes( t.Context(), actualReadWriteBucket, bufReadWriteBucket, ) require.NoError(t, err) assert.Empty(t, string(diff)) } func testGetBufProtocFileDescriptorSet(t *testing.T, dirPath string) *descriptorpb.FileDescriptorSet { data := testGetBufProtocFileDescriptorSetBytes(t, dirPath) fileDescriptorSet := &descriptorpb.FileDescriptorSet{} // TODO: change to image read logic require.NoError( t, protoencoding.NewWireUnmarshaler(nil).Unmarshal( data, fileDescriptorSet, ), ) return fileDescriptorSet } func testGetBufProtocFileDescriptorSetBytes(t *testing.T, dirPath string) []byte { stdout := bytes.NewBuffer(nil) appcmdtesting.Run( t, func(name string) *appcmd.Command { return NewCommand( name, appext.NewBuilder(name), ) }, appcmdtesting.WithStdout(stdout), appcmdtesting.WithExpectedExitCode(0), appcmdtesting.WithArgs( append( []string{ "-I", dirPath, "-o", "-", }, buftesting.GetProtocFilePaths(t, dirPath, 100)..., )..., ), ) return stdout.Bytes() } ================================================ FILE: cmd/buf/internal/command/alpha/protoc/testdata/1/flags.txt ================================================ -I proto --error_format text --go_out go_out --go_opt plugins=connect --plugin /bin/protoc-gen-go ================================================ FILE: cmd/buf/internal/command/alpha/protoc/testdata/2/flags1.txt ================================================ -I proto --error_format text @testdata/2/flags2.txt --go_opt plugins=connect --plugin /bin/protoc-gen-go ================================================ FILE: cmd/buf/internal/command/alpha/protoc/testdata/2/flags2.txt ================================================ --go_out go_out ================================================ FILE: cmd/buf/internal/command/alpha/protoc/testdata/3/flags1.txt ================================================ -I proto --error_format text @testdata/3/flags2.txt --go_opt plugins=connect --plugin /bin/protoc-gen-go ================================================ FILE: cmd/buf/internal/command/alpha/protoc/testdata/3/flags2.txt ================================================ --go_out go_out @testdata/3/flags1.txt ================================================ FILE: cmd/buf/internal/command/alpha/protoc/testdata/insertion/test.proto ================================================ syntax = "proto3"; package test; message Test { string name = 1; } ================================================ FILE: cmd/buf/internal/command/alpha/protoc/testdata/overlap/a/1.proto ================================================ ================================================ FILE: cmd/buf/internal/command/alpha/protoc/testdata/overlap/a/1.txt ================================================ ================================================ FILE: cmd/buf/internal/command/alpha/protoc/testdata/overlap/b/1.txt ================================================ ================================================ FILE: cmd/buf/internal/command/alpha/protoc/testdata/overlap/b/2.proto ================================================ ================================================ FILE: cmd/buf/internal/command/alpha/registry/token/tokendelete/tokendelete.go ================================================ // Copyright 2020-2026 Buf Technologies, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package tokendelete import ( "context" "buf.build/go/app/appcmd" "buf.build/go/app/appext" "connectrpc.com/connect" "github.com/bufbuild/buf/private/buf/bufcli" "github.com/bufbuild/buf/private/gen/proto/connect/buf/alpha/registry/v1alpha1/registryv1alpha1connect" registryv1alpha1 "github.com/bufbuild/buf/private/gen/proto/go/buf/alpha/registry/v1alpha1" "github.com/bufbuild/buf/private/pkg/connectclient" "github.com/bufbuild/buf/private/pkg/netext" "github.com/spf13/pflag" ) const ( forceFlagName = "force" tokenIDFlagName = "token-id" ) // NewCommand returns a new Command func NewCommand( name string, builder appext.SubCommandBuilder, ) *appcmd.Command { flags := newFlags() return &appcmd.Command{ Use: name + " ", Short: "Delete a token by ID", Args: appcmd.ExactArgs(1), Run: builder.NewRunFunc( func(ctx context.Context, container appext.Container) error { return run(ctx, container, flags) }, ), BindFlags: flags.Bind, } } type flags struct { Force bool TokenID string } func newFlags() *flags { return &flags{} } func (f *flags) Bind(flagSet *pflag.FlagSet) { flagSet.BoolVar( &f.Force, forceFlagName, false, "Force deletion without confirming. Use with caution", ) flagSet.StringVar( &f.TokenID, tokenIDFlagName, "", "The ID of the token to delete", ) _ = appcmd.MarkFlagRequired(flagSet, tokenIDFlagName) } func run( ctx context.Context, container appext.Container, flags *flags, ) error { bufcli.WarnAlphaCommand(ctx, container) registryHostname := container.Arg(0) if _, err := netext.ValidateHostname(registryHostname); err != nil { return err } clientConfig, err := bufcli.NewConnectClientConfig(container) if err != nil { return err } service := connectclient.Make(clientConfig, registryHostname, registryv1alpha1connect.NewTokenServiceClient) if !flags.Force { if err := bufcli.PromptUserForDelete(container, "token", flags.TokenID); err != nil { return err } } if _, err := service.DeleteToken( ctx, connect.NewRequest(registryv1alpha1.DeleteTokenRequest_builder{ TokenId: flags.TokenID, }.Build()), ); err != nil { if connect.CodeOf(err) == connect.CodeNotFound { return bufcli.NewTokenNotFoundError(flags.TokenID) } return err } return nil } ================================================ FILE: cmd/buf/internal/command/alpha/registry/token/tokenget/tokenget.go ================================================ // Copyright 2020-2026 Buf Technologies, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package tokenget import ( "context" "fmt" "buf.build/go/app/appcmd" "buf.build/go/app/appext" "connectrpc.com/connect" "github.com/bufbuild/buf/private/buf/bufcli" "github.com/bufbuild/buf/private/buf/bufprint" "github.com/bufbuild/buf/private/gen/proto/connect/buf/alpha/registry/v1alpha1/registryv1alpha1connect" registryv1alpha1 "github.com/bufbuild/buf/private/gen/proto/go/buf/alpha/registry/v1alpha1" "github.com/bufbuild/buf/private/pkg/connectclient" "github.com/bufbuild/buf/private/pkg/netext" "github.com/bufbuild/buf/private/pkg/syserror" "github.com/spf13/pflag" ) const ( formatFlagName = "format" tokenIDFlagName = "token-id" ) // NewCommand returns a new Command func NewCommand( name string, builder appext.SubCommandBuilder, ) *appcmd.Command { flags := newFlags() return &appcmd.Command{ Use: name + " ", Short: "Get a token by ID", Args: appcmd.ExactArgs(1), Run: builder.NewRunFunc( func(ctx context.Context, container appext.Container) error { return run(ctx, container, flags) }, ), BindFlags: flags.Bind, } } type flags struct { Format string TokenID string } func newFlags() *flags { return &flags{} } func (f *flags) Bind(flagSet *pflag.FlagSet) { flagSet.StringVar( &f.Format, formatFlagName, bufprint.FormatText.String(), fmt.Sprintf(`The output format to use. Must be one of %s`, bufprint.AllFormatsString), ) flagSet.StringVar( &f.TokenID, tokenIDFlagName, "", "The ID of the token to get", ) _ = appcmd.MarkFlagRequired(flagSet, tokenIDFlagName) } func run( ctx context.Context, container appext.Container, flags *flags, ) error { bufcli.WarnAlphaCommand(ctx, container) registryHostname := container.Arg(0) if _, err := netext.ValidateHostname(registryHostname); err != nil { return err } format, err := bufprint.ParseFormat(flags.Format) if err != nil { return appcmd.WrapInvalidArgumentError(err) } clientConfig, err := bufcli.NewConnectClientConfig(container) if err != nil { return err } service := connectclient.Make(clientConfig, registryHostname, registryv1alpha1connect.NewTokenServiceClient) resp, err := service.GetToken( ctx, connect.NewRequest(registryv1alpha1.GetTokenRequest_builder{ TokenId: flags.TokenID, }.Build()), ) if err != nil { if connect.CodeOf(err) == connect.CodeNotFound { return bufcli.NewTokenNotFoundError(flags.TokenID) } return err } printer, err := bufprint.NewTokenPrinter(container.Stdout(), format) if err != nil { return syserror.Wrap(err) } return printer.PrintTokens(ctx, resp.Msg.GetToken()) } ================================================ FILE: cmd/buf/internal/command/alpha/registry/token/tokenlist/tokenlist.go ================================================ // Copyright 2020-2026 Buf Technologies, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package tokenlist import ( "context" "fmt" "buf.build/go/app/appcmd" "buf.build/go/app/appext" "connectrpc.com/connect" "github.com/bufbuild/buf/private/buf/bufcli" "github.com/bufbuild/buf/private/buf/bufprint" "github.com/bufbuild/buf/private/gen/proto/connect/buf/alpha/registry/v1alpha1/registryv1alpha1connect" registryv1alpha1 "github.com/bufbuild/buf/private/gen/proto/go/buf/alpha/registry/v1alpha1" "github.com/bufbuild/buf/private/pkg/connectclient" "github.com/bufbuild/buf/private/pkg/netext" "github.com/bufbuild/buf/private/pkg/syserror" "github.com/spf13/pflag" ) const ( pageSizeFlagName = "page-size" pageTokenFlagName = "page-token" reverseFlagName = "reverse" formatFlagName = "format" ) // NewCommand returns a new Command func NewCommand( name string, builder appext.SubCommandBuilder, ) *appcmd.Command { flags := newFlags() return &appcmd.Command{ Use: name + " ", Short: "List user tokens", Args: appcmd.ExactArgs(1), Run: builder.NewRunFunc( func(ctx context.Context, container appext.Container) error { return run(ctx, container, flags) }, ), BindFlags: flags.Bind, } } type flags struct { PageSize uint32 PageToken string Reverse bool Format string } func newFlags() *flags { return &flags{} } func (f *flags) Bind(flagSet *pflag.FlagSet) { flagSet.Uint32Var(&f.PageSize, pageSizeFlagName, 10, `The page size.`, ) flagSet.StringVar(&f.PageToken, pageTokenFlagName, "", `The page token. If more results are available, a "next_page" key is present in the --format=json output.`, ) flagSet.BoolVar(&f.Reverse, reverseFlagName, false, `Reverse the results.`, ) flagSet.StringVar( &f.Format, formatFlagName, bufprint.FormatText.String(), fmt.Sprintf(`The output format to use. Must be one of %s`, bufprint.AllFormatsString), ) } func run( ctx context.Context, container appext.Container, flags *flags, ) (retErr error) { bufcli.WarnAlphaCommand(ctx, container) registryHostname := container.Arg(0) if _, err := netext.ValidateHostname(registryHostname); err != nil { return err } format, err := bufprint.ParseFormat(flags.Format) if err != nil { return appcmd.WrapInvalidArgumentError(err) } clientConfig, err := bufcli.NewConnectClientConfig(container) if err != nil { return err } service := connectclient.Make(clientConfig, registryHostname, registryv1alpha1connect.NewTokenServiceClient) resp, err := service.ListTokens( ctx, connect.NewRequest(registryv1alpha1.ListTokensRequest_builder{ PageSize: flags.PageSize, PageToken: flags.PageToken, Reverse: flags.Reverse, }.Build()), ) if err != nil { return err } printer, err := bufprint.NewTokenPrinter(container.Stdout(), format) if err != nil { return syserror.Wrap(err) } // TODO: flag docs say "next_token" field will be present in the output, // for paging through results but we are actually ignoring that field now. return printer.PrintTokens(ctx, resp.Msg.GetTokens()...) } ================================================ FILE: cmd/buf/internal/command/beta/bufpluginv1/bufpluginv1.go ================================================ // Copyright 2020-2026 Buf Technologies, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package bufpluginv1 import ( "buf.build/go/app/appcmd" "buf.build/go/app/appext" "github.com/bufbuild/buf/cmd/buf/internal/command/beta/internal" "github.com/bufbuild/buf/private/bufpkg/bufcheck/bufcheckserver" ) // NewCommand returns a new Command. func NewCommand(name string, builder appext.SubCommandBuilder) *appcmd.Command { return internal.NewCommand(name, builder, bufcheckserver.V1Spec) } ================================================ FILE: cmd/buf/internal/command/beta/bufpluginv1beta1/bufpluginv1beta1.go ================================================ // Copyright 2020-2026 Buf Technologies, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package bufpluginv1beta1 import ( "buf.build/go/app/appcmd" "buf.build/go/app/appext" "github.com/bufbuild/buf/cmd/buf/internal/command/beta/internal" "github.com/bufbuild/buf/private/bufpkg/bufcheck/bufcheckserver" ) // NewCommand returns a new Command. func NewCommand(name string, builder appext.SubCommandBuilder) *appcmd.Command { return internal.NewCommand(name, builder, bufcheckserver.V1Beta1Spec) } ================================================ FILE: cmd/buf/internal/command/beta/bufpluginv2/bufpluginv2.go ================================================ // Copyright 2020-2026 Buf Technologies, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package bufpluginv2 import ( "buf.build/go/app/appcmd" "buf.build/go/app/appext" "github.com/bufbuild/buf/cmd/buf/internal/command/beta/internal" "github.com/bufbuild/buf/private/bufpkg/bufcheck/bufcheckserver" ) // NewCommand returns a new Command. func NewCommand(name string, builder appext.SubCommandBuilder) *appcmd.Command { return internal.NewCommand(name, builder, bufcheckserver.V2Spec) } ================================================ FILE: cmd/buf/internal/command/beta/internal/internal.go ================================================ // Copyright 2020-2026 Buf Technologies, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package internal import ( "context" "buf.build/go/app" "buf.build/go/app/appcmd" "buf.build/go/app/appext" "buf.build/go/bufplugin/check" "github.com/spf13/pflag" "pluginrpc.com/pluginrpc" ) // NewCommand returns a new Command. func NewCommand( name string, builder appext.SubCommandBuilder, spec *check.Spec, ) *appcmd.Command { flags := newFlags() return &appcmd.Command{ Use: name, Short: "Run buf as a check plugin.", Run: builder.NewRunFunc( func(ctx context.Context, container appext.Container) error { return run(ctx, container, flags, spec) }, ), BindFlags: flags.Bind, } } type flags struct { Protocol bool Spec bool Format string } func newFlags() *flags { return &flags{} } func (f *flags) Bind(flagSet *pflag.FlagSet) { flagSet.BoolVar(&f.Protocol, pluginrpc.ProtocolFlagName, false, "Passed through to plugin.") flagSet.BoolVar(&f.Spec, pluginrpc.SpecFlagName, false, "Passed through to plugin.") flagSet.StringVar(&f.Format, pluginrpc.FormatFlagName, pluginrpc.FormatBinary.String(), "Passed through to plugin.") } func run( ctx context.Context, container appext.Container, flags *flags, spec *check.Spec, ) error { server, err := check.NewServer(spec) if err != nil { return err } args := app.Args(container) if flags.Protocol { args = append(args, "--"+pluginrpc.ProtocolFlagName) } if flags.Spec { args = append(args, "--"+pluginrpc.SpecFlagName) } if flags.Format != "" { args = append(args, "--"+pluginrpc.FormatFlagName+"="+flags.Format) } return server.Serve( ctx, pluginrpc.Env{ Args: args, Stdin: container.Stdin(), Stdout: container.Stdout(), Stderr: container.Stderr(), }, ) } ================================================ FILE: cmd/buf/internal/command/beta/price/price.go ================================================ // Copyright 2020-2026 Buf Technologies, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package price import ( "context" "fmt" "math" "text/template" "buf.build/go/app/appcmd" "buf.build/go/app/appext" "github.com/bufbuild/buf/private/buf/bufcli" "github.com/bufbuild/buf/private/buf/bufctl" "github.com/bufbuild/buf/private/bufpkg/bufmodule" "github.com/bufbuild/buf/private/pkg/protostat" "github.com/bufbuild/buf/private/pkg/protostat/protostatstorage" "github.com/spf13/pflag" ) const ( disableSymlinksFlagName = "disable-symlinks" teamsDollarsPerType = float64(0.50) proDollarsPerType = float64(5) proDollarsMinimumSpend = float64(3000) tmplCopy = `Current BSR pricing: - Teams: $0.50 per type - Pro: $5.00 per type, with a minimum spend of $3,000 per month Pricing data last updated on February 1, 2025. Make sure you are on the latest version of the Buf CLI to get the most updated pricing information, and see buf.build/pricing if in doubt - this command runs completely locally and does not interact with our servers. Your sources have: - {{.Messages}} messages - {{.Enums}} enums - {{.RPCs}} RPCs This adds up to {{.Types}} types. Based on this, these sources will cost: - ${{.TeamsDollarsPerMonth}}/month for Teams - ${{.ProDollarsPerMonth}}/month for Pro These values should be treated as an estimate - we price based on the average number of private types you have on the BSR during your billing period. ` ) // NewCommand returns a new Command. func NewCommand( name string, builder appext.SubCommandBuilder, ) *appcmd.Command { flags := newFlags() return &appcmd.Command{ Use: name + " ", Short: "Get the price for BSR paid plans for a given source or module", Long: bufcli.GetSourceOrModuleLong(`the source or module to get a price for`), Args: appcmd.MaximumNArgs(1), Run: builder.NewRunFunc( func(ctx context.Context, container appext.Container) error { return run(ctx, container, flags) }, ), BindFlags: flags.Bind, } } type flags struct { DisableSymlinks bool // special InputHashtag string } func newFlags() *flags { return &flags{} } func (f *flags) Bind(flagSet *pflag.FlagSet) { bufcli.BindDisableSymlinks(flagSet, &f.DisableSymlinks, disableSymlinksFlagName) bufcli.BindInputHashtag(flagSet, &f.InputHashtag) } func run( ctx context.Context, container appext.Container, flags *flags, ) error { input, err := bufcli.GetInputValue(container, flags.InputHashtag, ".") if err != nil { return err } controller, err := bufcli.NewController( container, bufctl.WithDisableSymlinks(flags.DisableSymlinks), ) if err != nil { return err } workspace, err := controller.GetWorkspace( ctx, input, ) if err != nil { return err } stats, err := protostat.GetStats( ctx, protostatstorage.NewFileWalker( bufmodule.ModuleReadBucketToStorageReadBucket( bufmodule.ModuleSetToModuleReadBucketWithOnlyProtoFilesForTargetModules( workspace, ), ), ), ) if err != nil { return err } tmpl, err := template.New("tmpl").Parse(tmplCopy) if err != nil { return err } return tmpl.Execute( container.Stdout(), newTmplData(stats), ) } type tmplData struct { *protostat.Stats TeamsDollarsPerMonth string ProDollarsPerMonth string } func newTmplData(stats *protostat.Stats) *tmplData { tmplData := &tmplData{ Stats: stats, } tmplData.TeamsDollarsPerMonth = fmt.Sprintf("%.2f", float64(tmplData.Types)*teamsDollarsPerType) tmplData.ProDollarsPerMonth = fmt.Sprintf("%.2f", math.Max(float64(tmplData.Types)*proDollarsPerType, proDollarsMinimumSpend)) return tmplData } ================================================ FILE: cmd/buf/internal/command/beta/registry/plugin/plugindelete/plugindelete.go ================================================ // Copyright 2020-2026 Buf Technologies, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package plugindelete import ( "context" "fmt" "strings" "buf.build/go/app/appcmd" "buf.build/go/app/appext" "connectrpc.com/connect" "github.com/bufbuild/buf/private/buf/bufcli" "github.com/bufbuild/buf/private/bufpkg/bufremoteplugin/bufremotepluginref" "github.com/bufbuild/buf/private/gen/proto/connect/buf/alpha/registry/v1alpha1/registryv1alpha1connect" registryv1alpha1 "github.com/bufbuild/buf/private/gen/proto/go/buf/alpha/registry/v1alpha1" "github.com/bufbuild/buf/private/pkg/connectclient" "github.com/spf13/pflag" ) // NewCommand returns a new Command. func NewCommand( name string, builder appext.SubCommandBuilder, ) *appcmd.Command { flags := newFlags() return &appcmd.Command{ Use: name + " ", Short: "Delete a plugin from the registry", Args: appcmd.ExactArgs(1), Run: builder.NewRunFunc( func(ctx context.Context, container appext.Container) error { return run(ctx, container, flags) }, ), BindFlags: flags.Bind, } } type flags struct{} func newFlags() *flags { return &flags{} } func (f *flags) Bind(flagSet *pflag.FlagSet) {} func run( ctx context.Context, container appext.Container, flags *flags, ) error { bufcli.WarnBetaCommand(ctx, container) identity, version, _ := strings.Cut(container.Arg(0), ":") pluginIdentity, err := bufremotepluginref.PluginIdentityForString(identity) if err != nil { return appcmd.WrapInvalidArgumentError(err) } if version != "" { if err := bufremotepluginref.ValidatePluginVersion(version); err != nil { return appcmd.WrapInvalidArgumentError(err) } } clientConfig, err := bufcli.NewConnectClientConfig(container) if err != nil { return err } service := connectclient.Make( clientConfig, pluginIdentity.Remote(), registryv1alpha1connect.NewPluginCurationServiceClient, ) if _, err := service.DeleteCuratedPlugin( ctx, connect.NewRequest( registryv1alpha1.DeleteCuratedPluginRequest_builder{ Owner: pluginIdentity.Owner(), Name: pluginIdentity.Plugin(), Version: version, }.Build(), ), ); err != nil { if connect.CodeOf(err) == connect.CodeNotFound { return fmt.Errorf("the plugin %s does not exist", container.Arg(0)) } return err } return nil } ================================================ FILE: cmd/buf/internal/command/beta/registry/plugin/pluginpush/pluginpush.go ================================================ // Copyright 2020-2026 Buf Technologies, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package pluginpush import ( "context" "errors" "fmt" "io/fs" "log/slog" "net/http" "os" "strings" "buf.build/go/app/appcmd" "buf.build/go/app/appext" "buf.build/go/standard/xstrings" "connectrpc.com/connect" "github.com/bufbuild/buf/private/buf/bufcli" "github.com/bufbuild/buf/private/buf/bufprint" "github.com/bufbuild/buf/private/bufpkg/bufremoteplugin" "github.com/bufbuild/buf/private/bufpkg/bufremoteplugin/bufremotepluginconfig" "github.com/bufbuild/buf/private/bufpkg/bufremoteplugin/bufremoteplugindocker" "github.com/bufbuild/buf/private/gen/proto/connect/buf/alpha/registry/v1alpha1/registryv1alpha1connect" registryv1alpha1 "github.com/bufbuild/buf/private/gen/proto/go/buf/alpha/registry/v1alpha1" "github.com/bufbuild/buf/private/pkg/connectclient" "github.com/bufbuild/buf/private/pkg/netext" "github.com/bufbuild/buf/private/pkg/netrc" "github.com/bufbuild/buf/private/pkg/storage" "github.com/bufbuild/buf/private/pkg/storage/storagearchive" "github.com/bufbuild/buf/private/pkg/storage/storageos" "github.com/bufbuild/buf/private/pkg/syserror" "github.com/google/go-containerregistry/pkg/authn" "github.com/google/go-containerregistry/pkg/name" pkgv1 "github.com/google/go-containerregistry/pkg/v1" "github.com/google/go-containerregistry/pkg/v1/remote" "github.com/google/go-containerregistry/pkg/v1/remote/transport" "github.com/spf13/pflag" ) const ( formatFlagName = "format" disableSymlinksFlagName = "disable-symlinks" overrideRemoteFlagName = "override-remote" imageFlagName = "image" visibilityFlagName = "visibility" publicVisibility = "public" privateVisibility = "private" ) var allVisibilityStrings = []string{ publicVisibility, privateVisibility, } // NewCommand returns a new Command. func NewCommand( name string, builder appext.SubCommandBuilder, ) *appcmd.Command { flags := newFlags() return &appcmd.Command{ Use: name + " ", Short: "Push a plugin to a registry", Long: bufcli.GetSourceDirLong(`the source to push (directory containing buf.plugin.yaml or plugin release zip)`), Args: appcmd.MaximumNArgs(1), Run: builder.NewRunFunc( func(ctx context.Context, container appext.Container) error { return run(ctx, container, flags) }, ), BindFlags: flags.Bind, } } type flags struct { Format string DisableSymlinks bool OverrideRemote string Image string Visibility string } func newFlags() *flags { return &flags{} } func (f *flags) Bind(flagSet *pflag.FlagSet) { bufcli.BindDisableSymlinks(flagSet, &f.DisableSymlinks, disableSymlinksFlagName) flagSet.StringVar( &f.Format, formatFlagName, bufprint.FormatText.String(), fmt.Sprintf(`The output format to use. Must be one of %s`, bufprint.AllFormatsString), ) flagSet.StringVar( &f.OverrideRemote, overrideRemoteFlagName, "", "Override the default remote found in buf.plugin.yaml name and dependencies", ) flagSet.StringVar( &f.Image, imageFlagName, "", "Existing image to push", ) flagSet.StringVar( &f.Visibility, visibilityFlagName, "", fmt.Sprintf(`The plugin's visibility setting. Must be one of %s`, xstrings.SliceToString(allVisibilityStrings)), ) _ = appcmd.MarkFlagRequired(flagSet, visibilityFlagName) } func run( ctx context.Context, container appext.Container, flags *flags, ) (retErr error) { bufcli.WarnBetaCommand(ctx, container) if len(flags.OverrideRemote) > 0 { if _, err := netext.ValidateHostname(flags.OverrideRemote); err != nil { return fmt.Errorf("%s: %w", overrideRemoteFlagName, err) } } format, err := bufprint.ParseFormat(flags.Format) if err != nil { return appcmd.WrapInvalidArgumentError(err) } source, err := bufcli.GetInputValue(container, "" /* The input hashtag is not supported here */, ".") if err != nil { return err } storageProvider := newStorageosProvider(flags.DisableSymlinks) sourceStat, err := os.Stat(source) if err != nil { return err } var sourceBucket storage.ReadWriteBucket if !sourceStat.IsDir() && strings.HasSuffix(strings.ToLower(sourceStat.Name()), ".zip") { // Unpack plugin release to temporary directory tmpDir, err := os.MkdirTemp(os.TempDir(), "plugin-push") if err != nil { return err } defer func() { if err := os.RemoveAll(tmpDir); !os.IsNotExist(err) { retErr = errors.Join(retErr, err) } }() sourceBucket, err = storageProvider.NewReadWriteBucket(tmpDir) if err != nil { return err } if err := unzipPluginToSourceBucket(ctx, source, sourceStat.Size(), sourceBucket); err != nil { return err } } else { sourceBucket, err = storageProvider.NewReadWriteBucket(source) if err != nil { return err } } existingConfigFilePath, err := bufremotepluginconfig.ExistingConfigFilePath(ctx, sourceBucket) if err != nil { return syserror.Wrap(err) } if existingConfigFilePath == "" { return fmt.Errorf("please define a %s configuration file in the target directory", bufremotepluginconfig.ExternalConfigFilePath) } var options []bufremotepluginconfig.ConfigOption if len(flags.OverrideRemote) > 0 { options = append(options, bufremotepluginconfig.WithOverrideRemote(flags.OverrideRemote)) } pluginConfig, err := bufremotepluginconfig.GetConfigForBucket(ctx, sourceBucket, options...) if err != nil { return err } client, err := bufremoteplugindocker.NewClient(container.Logger(), bufcli.Version) if err != nil { return err } defer func() { if err := client.Close(); err != nil { retErr = errors.Join(retErr, fmt.Errorf("docker client close error: %w", err)) } }() var imageID string if flags.Image != "" { inspectResponse, err := client.Inspect(ctx, flags.Image) if err != nil { return err } imageID = inspectResponse.ImageID } else { image, err := loadDockerImage(ctx, sourceBucket) if err != nil { return err } loadResponse, err := client.Load(ctx, image) if err != nil { return err } defer func() { if err := image.Close(); err != nil && !errors.Is(err, storage.ErrClosed) { retErr = errors.Join(retErr, fmt.Errorf("docker image close error: %w", err)) } }() imageID = loadResponse.ImageID } visibility, err := visibilityFlagToVisibility(flags.Visibility) if err != nil { return err } clientConfig, err := bufcli.NewConnectClientConfig(container) if err != nil { return err } service := connectclient.Make( clientConfig, pluginConfig.Name.Remote(), registryv1alpha1connect.NewPluginCurationServiceClient, ) latestPluginResp, err := service.GetLatestCuratedPlugin( ctx, connect.NewRequest( registryv1alpha1.GetLatestCuratedPluginRequest_builder{ Owner: pluginConfig.Name.Owner(), Name: pluginConfig.Name.Plugin(), Version: pluginConfig.PluginVersion, Revision: 0, // get latest revision for the plugin version. }.Build(), ), ) var currentImageDigest string var nextRevision uint32 if err != nil { if connect.CodeOf(err) != connect.CodeNotFound { return err } nextRevision = 1 } else { nextRevision = latestPluginResp.Msg.GetPlugin().GetRevision() + 1 currentImageDigest = latestPluginResp.Msg.GetPlugin().GetContainerImageDigest() } machine, err := netrc.GetMachineForName(container, pluginConfig.Name.Remote()) if err != nil { return err } authConfig := &bufremoteplugindocker.RegistryAuthConfig{} if machine != nil { authConfig.ServerAddress = machine.Name() authConfig.Username = machine.Login() authConfig.Password = machine.Password() } imageDigest, err := findExistingDigestForImageID(ctx, pluginConfig, authConfig, imageID, currentImageDigest) if err != nil { return err } if imageDigest == "" { imageDigest, err = pushImage(ctx, client, authConfig, pluginConfig, imageID) if err != nil { return err } } else { container.Logger().Info("image found in registry - skipping push") } plugin, err := bufremoteplugin.NewPlugin( pluginConfig.PluginVersion, pluginConfig.Dependencies, pluginConfig.Registry, imageDigest, pluginConfig.SourceURL, pluginConfig.Description, ) if err != nil { return err } createRequest, err := createCuratedPluginRequest(pluginConfig, plugin, nextRevision, visibility) if err != nil { return err } var curatedPlugin *registryv1alpha1.CuratedPlugin createPluginResp, err := service.CreateCuratedPlugin(ctx, connect.NewRequest(createRequest)) if err != nil { if connect.CodeOf(err) != connect.CodeAlreadyExists { return err } // Plugin with the same image digest and metadata already exists container.Logger().Info( "plugin already exists", slog.String("name", pluginConfig.Name.IdentityString()), slog.String("digest", plugin.ContainerImageDigest()), ) if latestPluginResp == nil { latestPluginResp, err = service.GetLatestCuratedPlugin( ctx, connect.NewRequest( registryv1alpha1.GetLatestCuratedPluginRequest_builder{ Owner: pluginConfig.Name.Owner(), Name: pluginConfig.Name.Plugin(), Version: pluginConfig.PluginVersion, Revision: 0, // get latest revision for the plugin version. }.Build(), ), ) if err != nil { return fmt.Errorf("unable to fetch latest plugin after AlreadyExists error: %w", err) } } // Ensure the image digest matches. if latestPluginResp.Msg.GetPlugin().GetContainerImageDigest() != plugin.ContainerImageDigest() { return fmt.Errorf("a plugin with the same name and version already exists, but with a different image digest (%s). If you want to push a new revision, please retry", latestPluginResp.Msg.GetPlugin().GetContainerImageDigest()) } curatedPlugin = latestPluginResp.Msg.GetPlugin() } else { curatedPlugin = createPluginResp.Msg.GetConfiguration() } return bufprint.NewCuratedPluginPrinter(container.Stdout()).PrintCuratedPlugin(ctx, format, curatedPlugin) } func createCuratedPluginRequest( pluginConfig *bufremotepluginconfig.Config, plugin bufremoteplugin.Plugin, nextRevision uint32, visibility registryv1alpha1.CuratedPluginVisibility, ) (*registryv1alpha1.CreateCuratedPluginRequest, error) { outputLanguages, err := bufremoteplugin.OutputLanguagesToProtoLanguages(pluginConfig.OutputLanguages) if err != nil { return nil, err } protoRegistryConfig, err := bufremoteplugin.PluginRegistryToProtoRegistryConfig(plugin.Registry()) if err != nil { return nil, err } return registryv1alpha1.CreateCuratedPluginRequest_builder{ Owner: pluginConfig.Name.Owner(), Name: pluginConfig.Name.Plugin(), RegistryType: bufremoteplugin.PluginToProtoPluginRegistryType(plugin), Version: plugin.Version(), ContainerImageDigest: plugin.ContainerImageDigest(), Dependencies: bufremoteplugin.PluginReferencesToCuratedProtoPluginReferences(plugin.Dependencies()), SourceUrl: plugin.SourceURL(), Description: plugin.Description(), RegistryConfig: protoRegistryConfig, Revision: nextRevision, OutputLanguages: outputLanguages, SpdxLicenseId: pluginConfig.SPDXLicenseID, LicenseUrl: pluginConfig.LicenseURL, Visibility: visibility, IntegrationGuideUrl: pluginConfig.IntegrationGuideURL, Deprecated: pluginConfig.Deprecated, }.Build(), nil } func pushImage( ctx context.Context, client bufremoteplugindocker.Client, authConfig *bufremoteplugindocker.RegistryAuthConfig, plugin *bufremotepluginconfig.Config, image string, ) (_ string, retErr error) { tagResponse, err := client.Tag(ctx, image, plugin) if err != nil { return "", err } createdImage := tagResponse.Image // We tag a Docker image using a unique ID label each time. // After we're done publishing the image, we delete it to not leave a lot of images left behind. defer func() { if _, err := client.Delete(ctx, createdImage); err != nil { retErr = errors.Join(retErr, fmt.Errorf("failed to delete image %q", createdImage)) } }() pushResponse, err := client.Push(ctx, createdImage, authConfig) if err != nil { return "", err } return pushResponse.Digest, nil } // findExistingDigestForImageID will query the OCI registry to see if the imageID already exists. // If an image is found with the same imageID, its digest will be returned (and we'll skip pushing to OCI registry). // // It performs the following search: // // - GET /v2/{owner}/{plugin}/tags/list // - For each tag: // - Fetch image: GET /v2/{owner}/{plugin}/manifests/{tag} // - If image manifest matches imageID, we can use the image digest for the image. func findExistingDigestForImageID( ctx context.Context, plugin *bufremotepluginconfig.Config, authConfig *bufremoteplugindocker.RegistryAuthConfig, imageID string, currentImageDigest string, ) (string, error) { repo, err := name.NewRepository(fmt.Sprintf("%s/%s/%s", plugin.Name.Remote(), plugin.Name.Owner(), plugin.Name.Plugin())) if err != nil { return "", err } auth := &authn.Basic{Username: authConfig.Username, Password: authConfig.Password} remoteOpts := []remote.Option{remote.WithContext(ctx), remote.WithAuth(auth)} // First attempt to see if the current image digest matches the image ID if currentImageDigest != "" { remoteImageID, _, err := getImageIDAndDigestFromReference(ctx, repo.Digest(currentImageDigest), remoteOpts...) if err != nil { return "", err } if remoteImageID == imageID { return currentImageDigest, nil } } // List all tags and check for a match tags, err := remote.List(repo, remoteOpts...) if err != nil { structuredErr := new(transport.Error) if errors.As(err, &structuredErr) { if structuredErr.StatusCode == http.StatusUnauthorized { return "", errors.New("you are not authenticated. For details, visit https://buf.build/docs/bsr/authentication") } if structuredErr.StatusCode == http.StatusNotFound { return "", nil } } return "", err } existingImageDigest := "" for _, tag := range tags { remoteImageID, imageDigest, err := getImageIDAndDigestFromReference(ctx, repo.Tag(tag), remoteOpts...) if err != nil { return "", err } if remoteImageID == imageID { existingImageDigest = imageDigest break } } return existingImageDigest, nil } // getImageIDAndDigestFromReference takes an image reference and returns 2 resolved digests: // // 1. The image config digest (https://github.com/opencontainers/image-spec/blob/v1.1.0/config.md) // 2. The image manifest digest (https://github.com/opencontainers/image-spec/blob/v1.1.0/manifest.md) // // The incoming ref is expected to be either an image manifest digest or an image index digest. func getImageIDAndDigestFromReference( ctx context.Context, ref name.Reference, options ...remote.Option, ) (string, string, error) { puller, err := remote.NewPuller(options...) if err != nil { return "", "", err } desc, err := puller.Get(ctx, ref) if err != nil { return "", "", err } switch { case desc.MediaType.IsIndex(): imageIndex, err := desc.ImageIndex() if err != nil { return "", "", fmt.Errorf("failed to get image index: %w", err) } indexManifest, err := imageIndex.IndexManifest() if err != nil { return "", "", fmt.Errorf("failed to get image manifests: %w", err) } var manifest pkgv1.Descriptor for _, desc := range indexManifest.Manifests { if p := desc.Platform; p != nil { // Drop attestations, which don't have a valid platform set. if p.OS == "unknown" && p.Architecture == "unknown" { continue } manifest = desc break } } if manifest.Digest.String() == "" { return "", "", fmt.Errorf("no valid platform manifest found in image index for %q", ref) } // We resolved the image index to an image manifest digest, we can now call this function // again to resolve the image manifest digest to an image config digest. return getImageIDAndDigestFromReference( ctx, ref.Context().Digest(manifest.Digest.String()), options..., ) case desc.MediaType.IsImage(): imageManifest, err := desc.Image() if err != nil { return "", "", fmt.Errorf("failed to get image: %w", err) } imageManifestDigest, err := imageManifest.Digest() if err != nil { return "", "", fmt.Errorf("failed to get image digest for %q: %w", ref, err) } manifest, err := imageManifest.Manifest() if err != nil { return "", "", fmt.Errorf("failed to get image manifest for %q: %w", ref, err) } return manifest.Config.Digest.String(), imageManifestDigest.String(), nil } return "", "", fmt.Errorf("unsupported media type: %q", desc.MediaType) } func unzipPluginToSourceBucket(ctx context.Context, pluginZip string, size int64, bucket storage.ReadWriteBucket) (retErr error) { f, err := os.Open(pluginZip) if err != nil { return err } defer func() { if err := f.Close(); err != nil { retErr = errors.Join(retErr, fmt.Errorf("plugin zip close error: %w", err)) } }() return storagearchive.Unzip(ctx, f, size, bucket) } func loadDockerImage(ctx context.Context, bucket storage.ReadBucket) (storage.ReadObjectCloser, error) { image, err := bucket.Get(ctx, bufremoteplugindocker.ImagePath) if errors.Is(err, fs.ErrNotExist) { return nil, fmt.Errorf("unable to find a %s plugin image: %w", bufremoteplugindocker.ImagePath, err) } return image, nil } func visibilityFlagToVisibility(visibility string) (registryv1alpha1.CuratedPluginVisibility, error) { switch visibility { case publicVisibility: return registryv1alpha1.CuratedPluginVisibility_CURATED_PLUGIN_VISIBILITY_PUBLIC, nil case privateVisibility: return registryv1alpha1.CuratedPluginVisibility_CURATED_PLUGIN_VISIBILITY_PRIVATE, nil default: return 0, fmt.Errorf("invalid visibility: %s, expected one of %s", visibility, xstrings.SliceToString(allVisibilityStrings)) } } func newStorageosProvider(disableSymlinks bool) storageos.Provider { var options []storageos.ProviderOption if !disableSymlinks { options = append(options, storageos.ProviderWithSymlinks()) } return storageos.NewProvider(options...) } ================================================ FILE: cmd/buf/internal/command/beta/registry/webhook/webhookcreate/webhookcreate.go ================================================ // Copyright 2020-2026 Buf Technologies, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package webhookcreate import ( "context" "encoding/json" "fmt" "buf.build/go/app/appcmd" "buf.build/go/app/appext" "connectrpc.com/connect" "github.com/bufbuild/buf/private/buf/bufcli" "github.com/bufbuild/buf/private/gen/proto/connect/buf/alpha/registry/v1alpha1/registryv1alpha1connect" registryv1alpha1 "github.com/bufbuild/buf/private/gen/proto/go/buf/alpha/registry/v1alpha1" "github.com/bufbuild/buf/private/pkg/connectclient" "github.com/spf13/pflag" ) const ( ownerFlagName = "owner" repositoryFlagName = "repository" callbackURLFlagName = "callback-url" webhookEventFlagName = "event" remoteFlagName = "remote" ) // NewCommand returns a new Command func NewCommand( name string, builder appext.SubCommandBuilder, ) *appcmd.Command { flags := newFlags() return &appcmd.Command{ Use: name, Short: "Create a repository webhook", Args: appcmd.ExactArgs(0), Run: builder.NewRunFunc( func(ctx context.Context, container appext.Container) error { return run(ctx, container, flags) }, ), BindFlags: flags.Bind, } } type flags struct { WebhookEvent string OwnerName string RepositoryName string CallbackURL string Remote string } func newFlags() *flags { return &flags{} } func (f *flags) Bind(flagSet *pflag.FlagSet) { flagSet.StringVar( &f.WebhookEvent, webhookEventFlagName, "", "The event type to create a webhook for. The proto enum string value is used for this input (e.g. 'WEBHOOK_EVENT_REPOSITORY_PUSH')", ) _ = appcmd.MarkFlagRequired(flagSet, webhookEventFlagName) flagSet.StringVar( &f.OwnerName, ownerFlagName, "", `The owner name of the repository to create a webhook for`, ) _ = appcmd.MarkFlagRequired(flagSet, ownerFlagName) flagSet.StringVar( &f.RepositoryName, repositoryFlagName, "", "The repository name to create a webhook for", ) _ = appcmd.MarkFlagRequired(flagSet, repositoryFlagName) flagSet.StringVar( &f.CallbackURL, callbackURLFlagName, "", "The url for the webhook to callback to on a given event", ) _ = appcmd.MarkFlagRequired(flagSet, callbackURLFlagName) flagSet.StringVar( &f.Remote, remoteFlagName, "", "The remote of the repository the created webhook will belong to", ) _ = appcmd.MarkFlagRequired(flagSet, remoteFlagName) } func run( ctx context.Context, container appext.Container, flags *flags, ) error { bufcli.WarnBetaCommand(ctx, container) clientConfig, err := bufcli.NewConnectClientConfig(container) if err != nil { return err } service := connectclient.Make(clientConfig, flags.Remote, registryv1alpha1connect.NewWebhookServiceClient) event, ok := registryv1alpha1.WebhookEvent_value[flags.WebhookEvent] if !ok || event == int32(registryv1alpha1.WebhookEvent_WEBHOOK_EVENT_UNSPECIFIED) { return fmt.Errorf("webhook event must be specified") } resp, err := service.CreateWebhook( ctx, connect.NewRequest( registryv1alpha1.CreateWebhookRequest_builder{ WebhookEvent: registryv1alpha1.WebhookEvent(event), OwnerName: flags.OwnerName, RepositoryName: flags.RepositoryName, CallbackUrl: flags.CallbackURL, }.Build(), ), ) if err != nil { return err } webhookJSON, err := json.MarshalIndent(resp.Msg.GetWebhook(), "", "\t") if err != nil { return err } // Ignore errors for writing to stdout. _, _ = container.Stdout().Write(webhookJSON) return nil } ================================================ FILE: cmd/buf/internal/command/beta/registry/webhook/webhookdelete/webhookdelete.go ================================================ // Copyright 2020-2026 Buf Technologies, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package webhookdelete import ( "context" "buf.build/go/app/appcmd" "buf.build/go/app/appext" "connectrpc.com/connect" "github.com/bufbuild/buf/private/buf/bufcli" "github.com/bufbuild/buf/private/gen/proto/connect/buf/alpha/registry/v1alpha1/registryv1alpha1connect" registryv1alpha1 "github.com/bufbuild/buf/private/gen/proto/go/buf/alpha/registry/v1alpha1" "github.com/bufbuild/buf/private/pkg/connectclient" "github.com/spf13/pflag" ) const ( webhookIDFlagName = "id" remoteFlagName = "remote" ) // NewCommand returns a new Command func NewCommand( name string, builder appext.SubCommandBuilder, ) *appcmd.Command { flags := newFlags() return &appcmd.Command{ Use: name, Short: "Delete a repository webhook", Args: appcmd.ExactArgs(0), Run: builder.NewRunFunc( func(ctx context.Context, container appext.Container) error { return run(ctx, container, flags) }, ), BindFlags: flags.Bind, } } type flags struct { WebhookID string Remote string } func newFlags() *flags { return &flags{} } func (f *flags) Bind(flagSet *pflag.FlagSet) { flagSet.StringVar( &f.WebhookID, webhookIDFlagName, "", "The webhook ID to delete", ) _ = appcmd.MarkFlagRequired(flagSet, webhookIDFlagName) flagSet.StringVar( &f.Remote, remoteFlagName, "", "The remote of the repository the webhook ID belongs to", ) _ = appcmd.MarkFlagRequired(flagSet, remoteFlagName) } func run( ctx context.Context, container appext.Container, flags *flags, ) error { bufcli.WarnBetaCommand(ctx, container) clientConfig, err := bufcli.NewConnectClientConfig(container) if err != nil { return err } service := connectclient.Make(clientConfig, flags.Remote, registryv1alpha1connect.NewWebhookServiceClient) if _, err := service.DeleteWebhook( ctx, connect.NewRequest( registryv1alpha1.DeleteWebhookRequest_builder{ WebhookId: flags.WebhookID, }.Build(), ), ); err != nil { return err } return nil } ================================================ FILE: cmd/buf/internal/command/beta/registry/webhook/webhooklist/webhooklist.go ================================================ // Copyright 2020-2026 Buf Technologies, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package webhooklist import ( "context" "encoding/json" "buf.build/go/app/appcmd" "buf.build/go/app/appext" "connectrpc.com/connect" "github.com/bufbuild/buf/private/buf/bufcli" "github.com/bufbuild/buf/private/gen/proto/connect/buf/alpha/registry/v1alpha1/registryv1alpha1connect" registryv1alpha1 "github.com/bufbuild/buf/private/gen/proto/go/buf/alpha/registry/v1alpha1" "github.com/bufbuild/buf/private/pkg/connectclient" "github.com/spf13/pflag" ) const ( ownerFlagName = "owner" repositoryFlagName = "repository" remoteFlagName = "remote" ) // NewCommand returns a new Command func NewCommand( name string, builder appext.SubCommandBuilder, ) *appcmd.Command { flags := newFlags() return &appcmd.Command{ Use: name, Short: "List repository webhooks", Args: appcmd.ExactArgs(0), Run: builder.NewRunFunc( func(ctx context.Context, container appext.Container) error { return run(ctx, container, flags) }, ), BindFlags: flags.Bind, } } type flags struct { OwnerName string RepositoryName string Remote string } func newFlags() *flags { return &flags{} } func (f *flags) Bind(flagSet *pflag.FlagSet) { flagSet.StringVar( &f.OwnerName, ownerFlagName, "", `The owner name of the repository to list webhooks for`, ) _ = appcmd.MarkFlagRequired(flagSet, ownerFlagName) flagSet.StringVar( &f.RepositoryName, repositoryFlagName, "", "The repository name to list webhooks for.", ) _ = appcmd.MarkFlagRequired(flagSet, repositoryFlagName) flagSet.StringVar( &f.Remote, remoteFlagName, "", "The remote of the owner and repository to list webhooks for", ) _ = appcmd.MarkFlagRequired(flagSet, remoteFlagName) } func run( ctx context.Context, container appext.Container, flags *flags, ) error { bufcli.WarnBetaCommand(ctx, container) clientConfig, err := bufcli.NewConnectClientConfig(container) if err != nil { return err } service := connectclient.Make(clientConfig, flags.Remote, registryv1alpha1connect.NewWebhookServiceClient) resp, err := service.ListWebhooks( ctx, connect.NewRequest( registryv1alpha1.ListWebhooksRequest_builder{ RepositoryName: flags.RepositoryName, OwnerName: flags.OwnerName, // TODO FUTURE: this should probably be in a loop so we can get page token from // response and query for the next page }.Build(), ), ) if err != nil { return err } if resp.Msg.GetWebhooks() == nil { // Ignore errors for writing to stdout. _, _ = container.Stdout().Write([]byte("[]")) return nil } webhooksJSON, err := json.MarshalIndent(resp.Msg.GetWebhooks(), "", "\t") if err != nil { return err } // Ignore errors for writing to stdout. _, _ = container.Stdout().Write(webhooksJSON) return nil } ================================================ FILE: cmd/buf/internal/command/beta/studioagent/studioagent.go ================================================ // Copyright 2020-2026 Buf Technologies, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package studioagent import ( "context" "crypto/tls" "fmt" "net" "buf.build/go/app/appcmd" "buf.build/go/app/appext" "buf.build/go/standard/xslices" "github.com/bufbuild/buf/private/buf/bufstudioagent" "github.com/bufbuild/buf/private/pkg/cert/certclient" "github.com/bufbuild/buf/private/pkg/transport/http/httpserver" "github.com/spf13/pflag" ) const ( bindFlagName = "bind" portFlagName = "port" originFlagName = "origin" disallowedHeadersFlagName = "disallowed-header" forwardHeadersFlagName = "forward-header" caCertFlagName = "ca-cert" clientCertFlagName = "client-cert" clientKeyFlagName = "client-key" serverCertFlagName = "server-cert" serverKeyFlagName = "server-key" privateNetworkFlagName = "private-network" ) // NewCommand returns a new Command. func NewCommand( name string, builder appext.SubCommandBuilder, ) *appcmd.Command { flags := newFlags() return &appcmd.Command{ Use: name, Short: "Run an HTTP(S) server as the Studio agent", Args: appcmd.ExactArgs(0), Run: builder.NewRunFunc( func(ctx context.Context, container appext.Container) error { return run(ctx, container, flags) }, ), BindFlags: flags.Bind, } } type flags struct { BindAddress string Port string Origin string DisallowedHeaders []string ForwardHeaders map[string]string CACert string ClientCert string ClientKey string ServerCert string ServerKey string PrivateNetwork bool } func newFlags() *flags { return &flags{} } func (f *flags) Bind(flagSet *pflag.FlagSet) { flagSet.StringVar( &f.BindAddress, bindFlagName, "127.0.0.1", "The address to be exposed to accept HTTP requests", ) flagSet.StringVar( &f.Port, portFlagName, "8080", "The port to be exposed to accept HTTP requests", ) flagSet.StringVar( &f.Origin, originFlagName, "https://buf.build", "The allowed origin for CORS options", ) flagSet.StringSliceVar( &f.DisallowedHeaders, disallowedHeadersFlagName, nil, `The header names that are disallowed by this agent. When the agent receives an enveloped request with these headers set, it will return an error rather than forward the request to the target server. Multiple headers are appended if specified multiple times`, ) flagSet.StringToStringVar( &f.ForwardHeaders, forwardHeadersFlagName, nil, `The headers to be forwarded via the agent to the target server. Must be an equals sign separated key-value pair (like --forward-header=fromHeader1=toHeader1). Multiple header pairs are appended if specified multiple times`, ) flagSet.StringVar( &f.CACert, caCertFlagName, "", "The CA cert to be used in the client and server TLS configuration", ) flagSet.StringVar( &f.ClientCert, clientCertFlagName, "", "The cert to be used in the client TLS configuration", ) flagSet.StringVar( &f.ClientKey, clientKeyFlagName, "", "The key to be used in the client TLS configuration", ) flagSet.StringVar( &f.ServerCert, serverCertFlagName, "", "The cert to be used in the server TLS configuration", ) flagSet.StringVar( &f.ServerKey, serverKeyFlagName, "", "The key to be used in the server TLS configuration", ) flagSet.BoolVar( &f.PrivateNetwork, privateNetworkFlagName, false, `Use the agent with private network CORS`, ) } func run( ctx context.Context, container appext.Container, flags *flags, ) error { // CA cert pool is optional. If it is nil, TLS uses the host's root CA set. var rootCAConfig *tls.Config var err error if flags.CACert != "" { rootCAConfig, err = certclient.NewClientTLS(certclient.WithRootCertFilePaths(flags.CACert)) if err != nil { return err } } // client TLS config is optional. If it is nil, it uses the default configuration from http2.Transport. var clientTLSConfig *tls.Config if flags.ClientCert != "" || flags.ClientKey != "" { clientTLSConfig, err = newTLSConfig(rootCAConfig, flags.ClientCert, flags.ClientKey) if err != nil { return fmt.Errorf("cannot create new client TLS config: %w", err) } } // server TLS config is optional. If it is nil, we serve with a h2c handler. var serverTLSConfig *tls.Config if flags.ServerCert != "" || flags.ServerKey != "" { serverTLSConfig, err = newTLSConfig(rootCAConfig, flags.ServerCert, flags.ServerKey) if err != nil { return fmt.Errorf("cannot create new server TLS config: %w", err) } } mux := bufstudioagent.NewHandler( container.Logger(), flags.Origin, clientTLSConfig, xslices.ToStructMap(flags.DisallowedHeaders), flags.ForwardHeaders, flags.PrivateNetwork, ) var httpListenConfig net.ListenConfig httpListener, err := httpListenConfig.Listen(ctx, "tcp", fmt.Sprintf("%s:%s", flags.BindAddress, flags.Port)) if err != nil { return err } return httpserver.Run( ctx, container.Logger(), httpListener, mux, httpserver.RunWithTLSConfig( serverTLSConfig, ), ) } func newTLSConfig(baseConfig *tls.Config, certFile, keyFile string) (*tls.Config, error) { config := baseConfig.Clone() if config == nil { config = &tls.Config{ MinVersion: tls.VersionTLS12, } } cert, err := tls.LoadX509KeyPair(certFile, keyFile) if err != nil { return nil, fmt.Errorf("error creating x509 keypair from cert file %s and key file %s", certFile, keyFile) } config.Certificates = []tls.Certificate{cert} return config, nil } ================================================ FILE: cmd/buf/internal/command/breaking/breaking.go ================================================ // Copyright 2020-2026 Buf Technologies, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package breaking import ( "context" "errors" "fmt" "log/slog" "maps" "slices" modulev1 "buf.build/gen/go/bufbuild/registry/protocolbuffers/go/buf/registry/module/v1" "buf.build/go/app/appcmd" "buf.build/go/app/appext" "buf.build/go/standard/xslices" "buf.build/go/standard/xstrings" "connectrpc.com/connect" "github.com/bufbuild/buf/private/buf/bufcli" "github.com/bufbuild/buf/private/buf/bufctl" "github.com/bufbuild/buf/private/buf/buffetch" "github.com/bufbuild/buf/private/bufpkg/bufanalysis" "github.com/bufbuild/buf/private/bufpkg/bufcheck" "github.com/bufbuild/buf/private/bufpkg/bufconfig" "github.com/bufbuild/buf/private/bufpkg/bufimage" "github.com/bufbuild/buf/private/bufpkg/bufregistryapi/bufregistryapimodule" "github.com/bufbuild/buf/private/pkg/syserror" "github.com/bufbuild/buf/private/pkg/wasm" "github.com/spf13/pflag" ) const ( errorFormatFlagName = "error-format" excludeImportsFlagName = "exclude-imports" pathsFlagName = "path" limitToInputFilesFlagName = "limit-to-input-files" configFlagName = "config" againstFlagName = "against" againstConfigFlagName = "against-config" againstRegistryFlagName = "against-registry" excludePathsFlagName = "exclude-path" disableSymlinksFlagName = "disable-symlinks" ) // NewCommand returns a new Command. func NewCommand( name string, builder appext.SubCommandBuilder, ) *appcmd.Command { flags := newFlags() return &appcmd.Command{ Use: name + " --against ", Short: "Verify no breaking changes have been made", Long: `This command makes sure that the location has no breaking changes compared to the location. ` + bufcli.GetInputLong(`the source, module, or image to check for breaking changes`), Args: appcmd.MaximumNArgs(1), Run: builder.NewRunFunc( func(ctx context.Context, container appext.Container) error { return run(ctx, container, flags) }, ), BindFlags: flags.Bind, } } type flags struct { ErrorFormat string ExcludeImports bool LimitToInputFiles bool Paths []string Config string Against string AgainstConfig string AgainstRegistry bool ExcludePaths []string DisableSymlinks bool // special InputHashtag string } func newFlags() *flags { return &flags{} } func (f *flags) Bind(flagSet *pflag.FlagSet) { bufcli.BindPaths(flagSet, &f.Paths, pathsFlagName) bufcli.BindInputHashtag(flagSet, &f.InputHashtag) bufcli.BindExcludePaths(flagSet, &f.ExcludePaths, excludePathsFlagName) bufcli.BindDisableSymlinks(flagSet, &f.DisableSymlinks, disableSymlinksFlagName) flagSet.StringVar( &f.ErrorFormat, errorFormatFlagName, "text", fmt.Sprintf( "The format for build errors or check violations printed to stdout. Must be one of %s", xstrings.SliceToString(bufanalysis.AllFormatStrings), ), ) flagSet.BoolVar( &f.ExcludeImports, excludeImportsFlagName, false, "Exclude imports from breaking change detection.", ) flagSet.BoolVar( &f.LimitToInputFiles, limitToInputFilesFlagName, false, fmt.Sprintf( `Only run breaking checks against the files in the input When set, the against input contains only the files in the input Overrides --%s`, pathsFlagName, ), ) flagSet.StringVar( &f.Config, configFlagName, "", `The buf.yaml file or data to use for configuration`, ) flagSet.StringVar( &f.Against, againstFlagName, "", fmt.Sprintf( `Required, except if --%s is set. The source, module, or image to check against. Must be one of format %s`, againstRegistryFlagName, buffetch.AllFormatsString, ), ) flagSet.StringVar( &f.AgainstConfig, againstConfigFlagName, "", `The buf.yaml file or data to use to configure the against source, module, or image`, ) flagSet.BoolVar( &f.AgainstRegistry, againstRegistryFlagName, false, fmt.Sprintf( `Run breaking checks against the latest commit on the default branch in the registry. All modules in the input must have a name configured, otherwise this will fail. If a remote module is not found with the configured name, then this will fail. This cannot be set with --%s.`, againstFlagName, ), ) } func run( ctx context.Context, container appext.Container, flags *flags, ) (retErr error) { if err := validateFlags(flags); err != nil { return err } input, err := bufcli.GetInputValue(container, flags.InputHashtag, ".") if err != nil { return err } controller, err := bufcli.NewController( container, bufctl.WithDisableSymlinks(flags.DisableSymlinks), bufctl.WithFileAnnotationErrorFormat(flags.ErrorFormat), bufctl.WithFileAnnotationsToStdout(), ) if err != nil { return err } wasmRuntime, err := bufcli.NewWasmRuntime(ctx, container) if err != nil { return err } defer func() { retErr = errors.Join(retErr, wasmRuntime.Close(ctx)) }() // Do not exclude imports here. bufcheck's Client requires all imports. // Use bufcheck's BreakingWithExcludeImports. imageWithConfigs, checkClient, err := controller.GetTargetImageWithConfigsAndCheckClient( ctx, input, wasmRuntime, bufctl.WithTargetPaths(flags.Paths, flags.ExcludePaths), bufctl.WithConfigOverride(flags.Config), ) if err != nil { return err } // TODO: this doesn't actually work because we're using the same file paths for both sides // of the roots change, then we're torched externalPaths := flags.Paths if flags.LimitToInputFiles { externalPaths, err = getExternalPathsForImages(imageWithConfigs) if err != nil { return err } } var againstImages []bufimage.Image if flags.Against != "" { // Do not exclude imports here. bufcheck's Client requires all imports. // Use bufcheck's BreakingWithExcludeImports. againstImagesWithConfigs, _, err := controller.GetTargetImageWithConfigsAndCheckClient( ctx, flags.Against, wasm.UnimplementedRuntime, bufctl.WithTargetPaths(externalPaths, flags.ExcludePaths), bufctl.WithConfigOverride(flags.AgainstConfig), ) if err != nil { return err } // We do not require the check configs from the against target once built, so they can // be dropped here. againstImages, err = xslices.MapError( againstImagesWithConfigs, func(imageWithConfig bufctl.ImageWithConfig) (bufimage.Image, error) { againstImage, ok := imageWithConfig.(bufimage.Image) if !ok { return nil, syserror.New("imageWithConfig could not be converted to Image") } return againstImage, nil }, ) if err != nil { return err } } clientConfig, err := bufcli.NewConnectClientConfig(container) if err != nil { return err } moduleServiceProvider := bufregistryapimodule.NewClientProvider(clientConfig) if flags.AgainstRegistry { for _, imageWithConfig := range imageWithConfigs { moduleFullName := imageWithConfig.ModuleFullName() if moduleFullName == nil { if imageWithConfig.ModuleOpaqueID() == "" { // This can occur in the case of a [buffetch.MessageRef], where we resolve the message // ref directly from the bucket without building the [bufmodule.Module]. In that case, // we are unnable to use --against-registry. return fmt.Errorf("cannot use --%s with unnamed module", againstRegistryFlagName) } return fmt.Errorf( "cannot use --%s with unnamed module, %s", againstRegistryFlagName, imageWithConfig.ModuleOpaqueID(), ) } // Check commits exist for the module on the default label before we try to get the against image. // TODO: remove this check once we have error details to indicate that the against input // has no commits on the default label. We use the API directly here to avoid issues of conflating // dependency errors to empty module errors. commitServiceClient := moduleServiceProvider.V1CommitServiceClient(moduleFullName.Registry()) if _, err := commitServiceClient.GetCommits( ctx, connect.NewRequest( &modulev1.GetCommitsRequest{ ResourceRefs: []*modulev1.ResourceRef{ { Value: &modulev1.ResourceRef_Name_{ Name: &modulev1.ResourceRef_Name{ Owner: moduleFullName.Owner(), Module: moduleFullName.Name(), }, }, }, }, }, ), ); err != nil { if connect.CodeOf(err) == connect.CodeFailedPrecondition { // This error occurs when the against input is a module that has no commits on the default branch. // We ignore this case to support new modulues that have just been created in the registry. container.Logger().DebugContext( ctx, "ignoring empty module without commits on the default branch", slog.String("module", imageWithConfig.ModuleFullName().String()), ) continue } return err } againstImage, err := controller.GetImage( ctx, moduleFullName.String(), bufctl.WithTargetPaths(externalPaths, flags.ExcludePaths), bufctl.WithConfigOverride(flags.AgainstConfig), ) if err != nil { return err } againstImages = append(againstImages, againstImage) } } if len(imageWithConfigs) != len(againstImages) { // In the case where the input and against workspaces do not contain the same number of // images, this could happen if the input contains new module(s). However, we require // the number of images to match because of module-specific [bufconfig.BreakingConfig]. // This can result in a less satisfying UX when adding modules to a workspace. // // To mitigate this for users adding new modules to their workspace, for the case where // len(imageWithConfigs) > len(againstImages), if all modules in [imageWithConfigs] have // the same [bufconfig.BreakingConfig] (so no unique, module-specific [bufconfig.BreakingConfig]), // we query the [againstImages] for the matching modules and ignore any modules from // [imageWithConfigs] that are not found in [againstImages]. // // In the case where len(imageWithConfigs) < len(againstImages) or there are module-specific // [bufconfig.BreakingConfig], we still return an error. Also, if the roots change, we're // torched. (Issue #3641) if len(imageWithConfigs) > len(againstImages) && hasNoUniqueBreakingConfig(imageWithConfigs) { imageWithConfigs, err = filterImageWithConfigsNotInAgainstImages(imageWithConfigs, againstImages) if err != nil { return err } } else { return newInputAgainstImageCountError(len(imageWithConfigs), len(againstImages)) } } // We add all check configs (both lint and breaking) as related configs to check if plugins // have rules configured. // We allocated twice the size of imageWithConfigs for both lint and breaking configs. allCheckConfigs := make([]bufconfig.CheckConfig, 0, len(imageWithConfigs)*2) for _, imageWithConfig := range imageWithConfigs { allCheckConfigs = append(allCheckConfigs, imageWithConfig.LintConfig()) allCheckConfigs = append(allCheckConfigs, imageWithConfig.BreakingConfig()) } var allFileAnnotations []bufanalysis.FileAnnotation for i, imageWithConfig := range imageWithConfigs { breakingOptions := []bufcheck.BreakingOption{ bufcheck.WithPluginConfigs(imageWithConfig.PluginConfigs()...), bufcheck.WithPolicyConfigs(imageWithConfig.PolicyConfigs()...), bufcheck.WithRelatedCheckConfigs(allCheckConfigs...), } if flags.ExcludeImports { breakingOptions = append(breakingOptions, bufcheck.BreakingWithExcludeImports()) } if err := checkClient.Breaking( ctx, imageWithConfig.BreakingConfig(), imageWithConfig, againstImages[i], breakingOptions..., ); err != nil { var fileAnnotationSet bufanalysis.FileAnnotationSet if errors.As(err, &fileAnnotationSet) { allFileAnnotations = append(allFileAnnotations, fileAnnotationSet.FileAnnotations()...) } else { return err } } } if len(allFileAnnotations) > 0 { allFileAnnotationSet := bufanalysis.NewFileAnnotationSet(allFileAnnotations...) if err := bufanalysis.PrintFileAnnotationSet( container.Stdout(), allFileAnnotationSet, flags.ErrorFormat, ); err != nil { return err } return bufctl.ErrFileAnnotation } return nil } func getExternalPathsForImages[I bufimage.Image, S ~[]I](images S) ([]string, error) { externalPaths := make(map[string]struct{}) for _, image := range images { for _, imageFile := range image.Files() { externalPaths[imageFile.ExternalPath()] = struct{}{} } } return xslices.MapKeysToSlice(externalPaths), nil } func validateFlags(flags *flags) error { if flags.Against == "" && !flags.AgainstRegistry { return fmt.Errorf("Must set --%s or --%s", againstFlagName, againstRegistryFlagName) } if flags.Against != "" && flags.AgainstRegistry { return fmt.Errorf("Cannot set both --%s and --%s", againstFlagName, againstRegistryFlagName) } return nil } // hasNoUniqueBreakingConfig iterates through imageWithConfigs and checks to see if there // are any unique [bufconfig.BreakingConfig]. It returns true if all [bufconfig.BreakingConfig] // are the same across all the images. func hasNoUniqueBreakingConfig(imageWithConfigs []bufctl.ImageWithConfig) bool { var base bufconfig.BreakingConfig for _, imageWithConfig := range imageWithConfigs { if base == nil { base = imageWithConfig.BreakingConfig() continue } if !equalBreakingConfig(base, imageWithConfig.BreakingConfig()) { return false } base = imageWithConfig.BreakingConfig() } return true } // Checks if the specified [bufconfig.BreakingConfig]s are equal. Returns true if both // [bufconfig.BreakingConfig] have the same configuration parameters. func equalBreakingConfig(breakingConfig1, breakingConfig2 bufconfig.BreakingConfig) bool { if breakingConfig1.Disabled() == breakingConfig2.Disabled() && breakingConfig1.FileVersion() == breakingConfig2.FileVersion() && slices.Equal(breakingConfig1.UseIDsAndCategories(), breakingConfig2.UseIDsAndCategories()) && slices.Equal(breakingConfig1.ExceptIDsAndCategories(), breakingConfig2.ExceptIDsAndCategories()) && slices.Equal(breakingConfig1.IgnorePaths(), breakingConfig2.IgnorePaths()) && maps.EqualFunc( breakingConfig1.IgnoreIDOrCategoryToPaths(), breakingConfig2.IgnoreIDOrCategoryToPaths(), slices.Equal[[]string], ) && breakingConfig1.DisableBuiltin() == breakingConfig2.DisableBuiltin() && breakingConfig1.IgnoreUnstablePackages() == breakingConfig2.IgnoreUnstablePackages() { return true } return false } // A helper function for filtering out [bufctl.ImageWithConfig]s from [imagesWithConfig] // if there is no corresponding image in [againstImages]. We determine this based on image // file path. // // This assumes that len(imageWithConfigs) > len(againstImages). // We also expect that each image in [againstImages] is mapped only once to a single // imageWithConfig in [imagesWithConfig]. If an againstImage is found, then we don't check // it again. We also validate that each image in [againstImages] is mapped to an imageWithConfig // from [imageWithConfigs]. func filterImageWithConfigsNotInAgainstImages( imageWithConfigs []bufctl.ImageWithConfig, againstImages []bufimage.Image, ) ([]bufctl.ImageWithConfig, error) { foundAgainstImageIndices := make(map[int]struct{}) var filteredImageWithConfigs []bufctl.ImageWithConfig for _, imageWithConfig := range imageWithConfigs { for _, imageFile := range imageWithConfig.Files() { if imageFile.IsImport() { continue } var foundImage bufimage.Image for i, againstImage := range againstImages { if _, ok := foundAgainstImageIndices[i]; ok { continue } if againstFile := againstImage.GetFile(imageFile.Path()); againstFile != nil && !againstFile.IsImport() { foundAgainstImageIndices[i] = struct{}{} foundImage = againstImage break } } if foundImage != nil { filteredImageWithConfigs = append(filteredImageWithConfigs, imageWithConfig) break } } } // If we are unsuccessful in mapping all againstImages to a unique imageWithConfig, then // we return the same error message. if len(foundAgainstImageIndices) != len(againstImages) || len(againstImages) != len(filteredImageWithConfigs) { return nil, newInputAgainstImageCountError(len(imageWithConfigs), len(againstImages)) } return filteredImageWithConfigs, nil } func newInputAgainstImageCountError(lenImageWithConfigs, lenAgainstImages int) error { return fmt.Errorf( "input contained %d images, whereas against contained %d images", lenImageWithConfigs, lenAgainstImages, ) } ================================================ FILE: cmd/buf/internal/command/breaking/breaking_test.go ================================================ // Copyright 2020-2026 Buf Technologies, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package breaking import ( "context" "fmt" "io" "path/filepath" "testing" "buf.build/go/app/appcmd" "buf.build/go/app/appcmd/appcmdtesting" "buf.build/go/app/appext" "github.com/bufbuild/buf/cmd/buf/internal/internaltesting" ) // TestBreakingWorkspaceNewModuleWithImport tests that adding a new module to a // workspace does not produce false breaking change errors when the new module // imports files from an existing module. // // This is a regression test for the case where the new module's imported files // cause filterImageWithConfigsNotInAgainstImages to incorrectly match the new // module to an existing against image, stealing the match from the module that // actually owns those files. func TestBreakingWorkspaceNewModuleWithImport(t *testing.T) { t.Parallel() testRunStdoutStderr( t, nil, 0, "", "", filepath.Join("testdata", "workspace_new_module", "head"), "--against", filepath.Join("testdata", "workspace_new_module", "against"), ) } func testRunStdoutStderr( t *testing.T, stdin io.Reader, expectedExitCode int, expectedStdout string, expectedStderr string, args ...string, ) { t.Helper() appcmdtesting.Run( t, func(name string) *appcmd.Command { return NewCommand( name, appext.NewBuilder( name, appext.BuilderWithInterceptor( func(next func(context.Context, appext.Container) error) func(context.Context, appext.Container) error { return func(ctx context.Context, container appext.Container) error { err := next(ctx, container) if err == nil { return nil } return fmt.Errorf("Failure: %w", err) } }, ), ), ) }, appcmdtesting.WithExpectedExitCode(expectedExitCode), appcmdtesting.WithExpectedStdout(expectedStdout), appcmdtesting.WithExpectedStderr(expectedStderr), appcmdtesting.WithEnv(internaltesting.NewEnvFunc(t)), appcmdtesting.WithStdin(stdin), appcmdtesting.WithArgs(args...), ) } ================================================ FILE: cmd/buf/internal/command/breaking/testdata/workspace_new_module/against/b/b.proto ================================================ syntax = "proto3"; package b.v1; message Bar { string name = 1; } ================================================ FILE: cmd/buf/internal/command/breaking/testdata/workspace_new_module/against/buf.yaml ================================================ version: v2 modules: - path: b - path: c ================================================ FILE: cmd/buf/internal/command/breaking/testdata/workspace_new_module/against/c/c.proto ================================================ syntax = "proto3"; package c.v1; message Baz { string name = 1; } ================================================ FILE: cmd/buf/internal/command/breaking/testdata/workspace_new_module/head/a/a.proto ================================================ syntax = "proto3"; package a.v1; import "c.proto"; message Foo { c.v1.Baz baz = 1; } ================================================ FILE: cmd/buf/internal/command/breaking/testdata/workspace_new_module/head/b/b.proto ================================================ syntax = "proto3"; package b.v1; message Bar { string name = 1; } ================================================ FILE: cmd/buf/internal/command/breaking/testdata/workspace_new_module/head/buf.yaml ================================================ version: v2 modules: - path: a - path: b - path: c ================================================ FILE: cmd/buf/internal/command/breaking/testdata/workspace_new_module/head/c/c.proto ================================================ syntax = "proto3"; package c.v1; message Baz { string name = 1; } ================================================ FILE: cmd/buf/internal/command/build/build.go ================================================ // Copyright 2020-2026 Buf Technologies, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package build import ( "context" "fmt" "buf.build/go/app" "buf.build/go/app/appcmd" "buf.build/go/app/appext" "buf.build/go/standard/xstrings" "github.com/bufbuild/buf/private/buf/bufcli" "github.com/bufbuild/buf/private/buf/bufctl" "github.com/bufbuild/buf/private/buf/buffetch" "github.com/bufbuild/buf/private/bufpkg/bufanalysis" "github.com/bufbuild/buf/private/bufpkg/bufimage/bufimageutil" "github.com/spf13/pflag" ) const ( asFileDescriptorSetFlagName = "as-file-descriptor-set" errorFormatFlagName = "error-format" excludeImportsFlagName = "exclude-imports" excludeSourceInfoFlagName = "exclude-source-info" excludeSourceRetentionOptionsFlagName = "exclude-source-retention-options" pathsFlagName = "path" outputFlagName = "output" outputFlagShortName = "o" configFlagName = "config" excludePathsFlagName = "exclude-path" disableSymlinksFlagName = "disable-symlinks" typeFlagName = "type" ) // NewCommand returns a new Command. func NewCommand( name string, builder appext.SubCommandBuilder, ) *appcmd.Command { flags := newFlags() return &appcmd.Command{ Use: name + " ", Short: "Build Protobuf files into a Buf image", Long: bufcli.GetInputLong(`the source or module to build or image to convert`), Args: appcmd.MaximumNArgs(1), Run: builder.NewRunFunc( func(ctx context.Context, container appext.Container) error { return run(ctx, container, flags) }, ), BindFlags: flags.Bind, } } type flags struct { AsFileDescriptorSet bool ErrorFormat string ExcludeImports bool ExcludeSourceInfo bool ExcludeSourceRetentionOptions bool Paths []string Output string Config string ExcludePaths []string DisableSymlinks bool Types []string // special InputHashtag string } func newFlags() *flags { return &flags{} } func (f *flags) Bind(flagSet *pflag.FlagSet) { bufcli.BindInputHashtag(flagSet, &f.InputHashtag) bufcli.BindAsFileDescriptorSet(flagSet, &f.AsFileDescriptorSet, asFileDescriptorSetFlagName) bufcli.BindExcludeImports(flagSet, &f.ExcludeImports, excludeImportsFlagName) bufcli.BindExcludeSourceInfo(flagSet, &f.ExcludeSourceInfo, excludeSourceInfoFlagName) bufcli.BindPaths(flagSet, &f.Paths, pathsFlagName) bufcli.BindExcludePaths(flagSet, &f.ExcludePaths, excludePathsFlagName) bufcli.BindDisableSymlinks(flagSet, &f.DisableSymlinks, disableSymlinksFlagName) flagSet.BoolVar( &f.ExcludeSourceRetentionOptions, excludeSourceRetentionOptionsFlagName, false, "Exclude options whose retention is source", ) flagSet.StringVar( &f.ErrorFormat, errorFormatFlagName, "text", fmt.Sprintf( "The format for build errors printed to stderr. Must be one of %s", xstrings.SliceToString(bufanalysis.AllFormatStrings), ), ) flagSet.StringVarP( &f.Output, outputFlagName, outputFlagShortName, app.DevNullFilePath, fmt.Sprintf( `The output location for the built image. Must be one of format %s`, buffetch.MessageFormatsString, ), ) flagSet.StringVar( &f.Config, configFlagName, "", `The buf.yaml file or data to use for configuration`, ) flagSet.StringSliceVar( &f.Types, typeFlagName, nil, "The types (package, message, enum, extension, service, method) that should be included in this image. When specified, the resulting image will only include descriptors to describe the requested types", ) } func run( ctx context.Context, container appext.Container, flags *flags, ) error { if err := bufcli.ValidateRequiredFlag(outputFlagName, flags.Output); err != nil { return err } input, err := bufcli.GetInputValue(container, flags.InputHashtag, ".") if err != nil { return err } controller, err := bufcli.NewController( container, bufctl.WithDisableSymlinks(flags.DisableSymlinks), bufctl.WithFileAnnotationErrorFormat(flags.ErrorFormat), ) if err != nil { return err } image, err := controller.GetImage( ctx, input, bufctl.WithTargetPaths(flags.Paths, flags.ExcludePaths), bufctl.WithImageExcludeSourceInfo(flags.ExcludeSourceInfo), bufctl.WithImageExcludeImports(flags.ExcludeImports), bufctl.WithImageIncludeTypes(flags.Types), bufctl.WithConfigOverride(flags.Config), ) if err != nil { return err } if flags.ExcludeSourceRetentionOptions { image, err = bufimageutil.StripSourceRetentionOptions(image) if err != nil { return err } } return controller.PutImage( ctx, flags.Output, image, bufctl.WithImageAsFileDescriptorSet(flags.AsFileDescriptorSet), ) } ================================================ FILE: cmd/buf/internal/command/config/configinit/configinit.go ================================================ // Copyright 2020-2026 Buf Technologies, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package configinit import ( "context" "fmt" "buf.build/go/app/appcmd" "buf.build/go/app/appext" "github.com/bufbuild/buf/private/buf/bufcli" "github.com/bufbuild/buf/private/bufpkg/bufconfig" "github.com/bufbuild/buf/private/bufpkg/bufparse" "github.com/spf13/pflag" ) const ( documentationCommentsFlagName = "doc" outDirPathFlagName = "output" outDirPathFlagShortName = "o" uncommentFlagName = "uncomment" ) // NewCommand returns a new init Command. func NewCommand( name string, builder appext.SubCommandBuilder, deprecated string, hidden bool, bindOldFlags bool, ) *appcmd.Command { flags := newFlags(bindOldFlags) return &appcmd.Command{ Use: name + " [buf.build/owner/foobar]", Short: "Initialize buf configuration files for your local development", Long: `This command will write a new buf.yaml file to start your local development. If a buf.yaml already exists, this command will not overwrite it, and will produce an error. The effects of this command may change over time - this command may initialize i.e. buf.gen.yaml files in the future.`, Args: appcmd.MaximumNArgs(1), Deprecated: deprecated, Hidden: hidden, Run: builder.NewRunFunc( func(ctx context.Context, container appext.Container) error { return run(ctx, container, flags) }, ), BindFlags: flags.Bind, } } type flags struct { OutDirPath string // Hidden. DocumentationComments bool // Hidden. Uncomment bool bindOldFlags bool } func newFlags(bindOldFlags bool) *flags { return &flags{ bindOldFlags: bindOldFlags, } } func (f *flags) Bind(flagSet *pflag.FlagSet) { flagSet.StringVarP( &f.OutDirPath, outDirPathFlagName, outDirPathFlagShortName, ".", `The directory to write the configuration files to`, ) if f.bindOldFlags { // TODO FUTURE: Bring this flag back in future versions if we decide it's important. // We're not breaking anyone by not actually producing comments for now. flagSet.BoolVar( &f.DocumentationComments, documentationCommentsFlagName, false, "Write inline documentation in the form of comments in the resulting configuration file", ) _ = flagSet.MarkHidden(documentationCommentsFlagName) flagSet.BoolVar( &f.Uncomment, uncommentFlagName, false, "Uncomment examples in the resulting configuration file", ) _ = flagSet.MarkHidden(uncommentFlagName) } } func run( ctx context.Context, container appext.Container, flags *flags, ) error { if err := bufcli.ValidateRequiredFlag(outDirPathFlagName, flags.OutDirPath); err != nil { return err } exists, err := bufcli.BufYAMLFileExistsForDirPath(ctx, flags.OutDirPath) if err != nil { return err } if exists { return fmt.Errorf("buf.yaml already exists in directory %s, will not overwrite", flags.OutDirPath) } fileVersion := bufconfig.FileVersionV2 var moduleFullName bufparse.FullName if container.NumArgs() > 0 { moduleFullName, err = bufparse.ParseFullName(container.Arg(0)) if err != nil { return err } } moduleConfig, err := bufconfig.NewModuleConfig( ".", moduleFullName, // The default (empty) value for rootToIncludes and rootToExcludes only has key ".". map[string][]string{ ".": {}, }, map[string][]string{ ".": {}, }, bufconfig.NewLintConfig( bufconfig.NewEnabledCheckConfigForUseIDsAndCategories( fileVersion, []string{"STANDARD"}, false, ), "", false, false, false, "", // We actually want comment ignores enabled by default true, ), bufconfig.NewBreakingConfig( bufconfig.NewEnabledCheckConfigForUseIDsAndCategories( fileVersion, []string{"FILE"}, false, ), false, ), ) if err != nil { return err } bufYAMLFile, err := bufconfig.NewBufYAMLFile( fileVersion, []bufconfig.ModuleConfig{ moduleConfig, }, nil, nil, nil, bufconfig.BufYAMLFileWithIncludeDocsLink(), ) if err != nil { return err } return bufcli.PutBufYAMLFileForDirPath(ctx, flags.OutDirPath, bufYAMLFile) } ================================================ FILE: cmd/buf/internal/command/config/configlsbreakingrules/configlsbreakingrules.go ================================================ // Copyright 2020-2026 Buf Technologies, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package configlsbreakingrules import ( "buf.build/go/app/appcmd" "buf.build/go/app/appext" "buf.build/go/bufplugin/check" "github.com/bufbuild/buf/cmd/buf/internal/command/config/internal" ) // NewCommand returns a new Command. func NewCommand( name string, builder appext.SubCommandBuilder, ) *appcmd.Command { return internal.NewLSCommand( name, builder, check.RuleTypeBreaking, ) } ================================================ FILE: cmd/buf/internal/command/config/configlslintrules/configlslintrules.go ================================================ // Copyright 2020-2026 Buf Technologies, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package configlslintrules import ( "buf.build/go/app/appcmd" "buf.build/go/app/appext" "buf.build/go/bufplugin/check" "github.com/bufbuild/buf/cmd/buf/internal/command/config/internal" ) // NewCommand returns a new Command. func NewCommand( name string, builder appext.SubCommandBuilder, ) *appcmd.Command { return internal.NewLSCommand( name, builder, check.RuleTypeLint, ) } ================================================ FILE: cmd/buf/internal/command/config/configlsmodules/configlsmodules.go ================================================ // Copyright 2020-2026 Buf Technologies, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package configlsmodules import ( "context" "encoding/json" "errors" "fmt" "io/fs" "sort" "buf.build/go/app" "buf.build/go/app/appcmd" "buf.build/go/app/appext" "buf.build/go/standard/xslices" "buf.build/go/standard/xstrings" "github.com/bufbuild/buf/private/buf/bufcli" "github.com/bufbuild/buf/private/bufpkg/bufconfig" "github.com/bufbuild/buf/private/pkg/normalpath" "github.com/bufbuild/buf/private/pkg/syserror" "github.com/spf13/pflag" ) const ( configFlagName = "config" formatFlagName = "format" formatPath = "path" formatName = "name" formatJSON = "json" defaultFormat = formatPath ) var ( allFormats = []string{ formatPath, formatName, formatJSON, } ) // NewCommand returns a new Command. func NewCommand( name string, builder appext.SubCommandBuilder, ) *appcmd.Command { flags := newFlags() return &appcmd.Command{ Use: name, Short: "List configured modules", Args: appcmd.NoArgs, Run: builder.NewRunFunc( func(ctx context.Context, container appext.Container) error { return run(ctx, container, flags) }, ), BindFlags: flags.Bind, } } type flags struct { Config string Format string } func newFlags() *flags { return &flags{} } func (f *flags) Bind(flagSet *pflag.FlagSet) { flagSet.StringVar( &f.Config, configFlagName, "", `The buf.yaml file or data to use for configuration.`, ) flagSet.StringVar( &f.Format, formatFlagName, defaultFormat, fmt.Sprintf( "The format to print rules as. Must be one of %s", xstrings.SliceToString(allFormats), ), ) } func run( ctx context.Context, container appext.Container, flags *flags, ) error { externalModules, err := getExternalModules(ctx, flags.Config) if err != nil { return err } return printExternalModules(ctx, container, flags.Format, externalModules) } func getExternalModules( ctx context.Context, configOverride string, ) ([]*externalModule, error) { // If an override is specified, read buf.yaml from it. if configOverride != "" { bufYAMLFile, err := bufconfig.GetBufYAMLFileForOverride(configOverride) if err != nil { return nil, err } return getExternalModulesForBufYAMLFile(ctx, bufYAMLFile) } // First, look for a buf.work.yaml file. bufWorkYAMLFile, err := bufcli.GetBufWorkYAMLFileForDirPath(ctx, ".") if err != nil { if !errors.Is(err, fs.ErrNotExist) { return nil, err } // We do not have a buf.work.yaml file, attempt to read a buf.yaml file. bufYAMLFile, err := bufcli.GetBufYAMLFileForDirPath(ctx, ".") if err != nil { if !errors.Is(err, fs.ErrNotExist) { return nil, err } // We do not have a buf.work.yaml or buf.yaml file, use the default. bufYAMLFile, err = bufconfig.NewBufYAMLFile( bufconfig.FileVersionV2, []bufconfig.ModuleConfig{ bufconfig.DefaultModuleConfigV2, }, nil, nil, nil, ) if err != nil { return nil, err } } // This handles both buf.yaml file and no file courtesy of the above logic. return getExternalModulesForBufYAMLFile(ctx, bufYAMLFile) } // We did have a buf.work.yaml file, but before handling it, check there is not a buf.yaml. _, err = bufcli.GetBufYAMLFileForDirPath(ctx, ".") if err != nil && !errors.Is(err, fs.ErrNotExist) { return nil, err } if err == nil { return nil, errors.New("Both buf.work.yaml and buf.yaml found. It is not valid to have a buf.work.yaml and buf.yaml in the same directory, buf.work.yaml specifies a workspace of modules, while buf.yaml either specifies a single module or a workspace of modules itself.") } // Handle the buf.work.yaml. return getExternalModulesForBufWorkYAMLFile(ctx, bufWorkYAMLFile) } // This preserves directory order from the bufWorkYAMLFile. func getExternalModulesForBufWorkYAMLFile( ctx context.Context, bufWorkYAMLFile bufconfig.BufWorkYAMLFile, ) ([]*externalModule, error) { var externalModules []*externalModule for _, dirPath := range bufWorkYAMLFile.DirPaths() { bufYAMLFile, err := bufcli.GetBufYAMLFileForDirPath(ctx, dirPath) if err != nil { if !errors.Is(err, fs.ErrNotExist) { return nil, err } externalModules = append( externalModules, newExternalModule(dirPath, nil, nil, ""), ) continue } // This is a sanity check. Make sure we have what we expect. switch bufYAMLFile.FileVersion() { case bufconfig.FileVersionV1Beta1, bufconfig.FileVersionV1: moduleConfigs := bufYAMLFile.ModuleConfigs() if len(moduleConfigs) != 1 { return nil, syserror.Newf("got BufYAMLFile at %q with FileVersion %v with %d ModuleConfigs", dirPath, bufYAMLFile.FileVersion(), len(moduleConfigs)) } moduleConfig := moduleConfigs[0] if moduleConfig.DirPath() != "." { return nil, syserror.Newf("got BufYAMLFile at %q with FileVersion %v with ModuleConfig that had non-root DirPath %q", dirPath, bufYAMLFile.FileVersion(), moduleConfig.DirPath()) } var name string if moduleFullName := moduleConfig.FullName(); moduleFullName != nil { name = moduleFullName.String() } includes := xslices.Map(moduleConfig.RootToIncludes()["."], func(include string) string { return normalpath.Join(dirPath, include) }) excludes := xslices.Map(moduleConfig.RootToExcludes()["."], func(exclude string) string { return normalpath.Join(dirPath, exclude) }) externalModules = append( externalModules, // The dirPath is the path specified in the buf.work.yaml. // The DirPath for v1/v1beta1 ModuleConfigs is always ".". newExternalModule(dirPath, includes, excludes, name), ) case bufconfig.FileVersionV2: return nil, fmt.Errorf("buf.work.yaml pointed to directory %q which has a v2 buf.yaml file", dirPath) default: return nil, syserror.Newf("unknown FileVersion: %v", bufYAMLFile.FileVersion()) } } return externalModules, nil } // This preserves module config order from the bufYAMLFile. func getExternalModulesForBufYAMLFile( ctx context.Context, bufYAMLFile bufconfig.BufYAMLFile, ) ([]*externalModule, error) { moduleConfigs := bufYAMLFile.ModuleConfigs() externalModules := make([]*externalModule, len(moduleConfigs)) for i, moduleConfig := range moduleConfigs { var name string if moduleFullName := moduleConfig.FullName(); moduleFullName != nil { name = moduleFullName.String() } dirPath := moduleConfig.DirPath() includes := xslices.Map(moduleConfig.RootToIncludes()["."], func(include string) string { return normalpath.Join(dirPath, include) }) excludes := xslices.Map(moduleConfig.RootToExcludes()["."], func(exclude string) string { return normalpath.Join(dirPath, exclude) }) externalModules[i] = newExternalModule(dirPath, includes, excludes, name) } return externalModules, nil } func printExternalModules( ctx context.Context, container app.StdoutContainer, format string, externalModules []*externalModule, ) error { switch format { case formatPath: // Two modules may have the same path, SliceStable breaks the tie with the original ordering. sort.SliceStable( externalModules, func(i int, j int) bool { return externalModules[i].Path < externalModules[j].Path }, ) for _, externalModule := range externalModules { if _, err := container.Stdout().Write([]byte(externalModule.Path + "\n")); err != nil { return err } } return nil case formatName: sort.Slice( externalModules, func(i int, j int) bool { return externalModules[i].Name < externalModules[j].Name }, ) for _, externalModule := range externalModules { if externalModule.Name == "" { continue } if _, err := container.Stdout().Write([]byte(externalModule.Name + "\n")); err != nil { return err } } return nil case formatJSON: // Two modules may have the same path, SliceStable breaks the tie with the original ordering. sort.SliceStable( externalModules, func(i int, j int) bool { return externalModules[i].Path < externalModules[j].Path }, ) for _, externalModule := range externalModules { data, err := json.Marshal(externalModule) if err != nil { return err } if _, err := container.Stdout().Write([]byte(string(data) + "\n")); err != nil { return err } } return nil default: return appcmd.NewInvalidArgumentErrorf("unknown value for --%s: %s", formatFlagName, format) } } type externalModule struct { Path string `json:"path,omitempty" yaml:"path,omitempty"` Includes []string `json:"includes,omitempty" yaml:"includes,omitempty"` Excludes []string `json:"excludes,omitempty" yaml:"excludes,omitempty"` Name string `json:"name,omitempty" yaml:"name,omitempty"` } func newExternalModule(path string, includes []string, excludes []string, name string) *externalModule { return &externalModule{ Path: path, Includes: includes, Excludes: excludes, Name: name, } } ================================================ FILE: cmd/buf/internal/command/config/configmigrate/configmigrate.go ================================================ // Copyright 2020-2026 Buf Technologies, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package configmigrate import ( "context" "buf.build/go/app/appcmd" "buf.build/go/app/appext" "github.com/bufbuild/buf/private/buf/bufcli" "github.com/bufbuild/buf/private/buf/bufmigrate" "github.com/bufbuild/buf/private/pkg/storage/storageos" "github.com/spf13/pflag" ) const ( workspaceDirectoriesFlagName = "workspace" moduleDirectoriesFlagName = "module" bufGenYAMLFilePathFlagName = "buf-gen-yaml" diffFlagName = "diff" diffFlagShortName = "d" ) // ignoreDirPaths are paths we ignore when calling MigrateAll or DiffAll. // // This is just a hardcoded list for the 99% case. If users actually want to migrate files in one // of these locations, they can use the flags, but typically we do not want to pick these up. var ignoreDirPaths = []string{ ".git", ".github", } // NewCommand returns a new Command. func NewCommand( name string, builder appext.SubCommandBuilder, ) *appcmd.Command { flags := newFlags() return &appcmd.Command{ Use: name, Short: `Migrate all buf.yaml, buf.work.yaml, buf.gen.yaml, and buf.lock files at the specified directories or paths to v2`, Long: `If no flags are specified, the current directory is searched for buf.yamls, buf.work.yamls, and buf.gen.yamls. The effects of this command may change over time `, Args: appcmd.MaximumNArgs(0), Run: builder.NewRunFunc( func(ctx context.Context, container appext.Container) error { return run(ctx, container, flags) }, ), BindFlags: flags.Bind, } } type flags struct { WorkspaceDirPaths []string ModuleDirPaths []string BufGenYAMLFilePaths []string Diff bool } func newFlags() *flags { return &flags{} } func (f *flags) Bind(flagSet *pflag.FlagSet) { flagSet.StringSliceVar( &f.WorkspaceDirPaths, workspaceDirectoriesFlagName, nil, "The workspace directories to migrate. buf.work.yaml, buf.yamls and buf.locks will be migrated", ) flagSet.StringSliceVar( &f.ModuleDirPaths, moduleDirectoriesFlagName, nil, "The module directories to migrate. buf.yaml and buf.lock will be migrated", ) flagSet.StringSliceVar( &f.BufGenYAMLFilePaths, bufGenYAMLFilePathFlagName, nil, "The paths to the buf.gen.yaml generation templates to migrate", ) flagSet.BoolVarP( &f.Diff, diffFlagName, diffFlagShortName, false, "Write a diff to stdout instead of migrating files on disk. Useful for performing a dry run.", ) } func run( ctx context.Context, container appext.Container, flags *flags, ) error { moduleKeyProvider, err := bufcli.NewModuleKeyProvider(container) if err != nil { return err } commitProvider, err := bufcli.NewCommitProvider(container) if err != nil { return err } bucket, err := storageos.NewProvider(storageos.ProviderWithSymlinks()).NewReadWriteBucket( ".", storageos.ReadWriteBucketWithSymlinksIfSupported(), ) if err != nil { return err } migrator := bufmigrate.NewMigrator( container.Logger(), moduleKeyProvider, commitProvider, ) all := len(flags.WorkspaceDirPaths) == 0 && len(flags.ModuleDirPaths) == 0 && len(flags.BufGenYAMLFilePaths) == 0 if flags.Diff { if all { return bufmigrate.DiffAll( ctx, migrator, bucket, container.Stdout(), ignoreDirPaths, ) } return migrator.Diff( ctx, bucket, container.Stdout(), flags.WorkspaceDirPaths, flags.ModuleDirPaths, flags.BufGenYAMLFilePaths, ) } if all { return bufmigrate.MigrateAll( ctx, migrator, bucket, ignoreDirPaths, ) } return migrator.Migrate( ctx, bucket, flags.WorkspaceDirPaths, flags.ModuleDirPaths, flags.BufGenYAMLFilePaths, ) } ================================================ FILE: cmd/buf/internal/command/config/configmigrate/configmigrate_test.go ================================================ // Copyright 2020-2026 Buf Technologies, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package configmigrate import ( "bytes" "path/filepath" "testing" "buf.build/go/app/appcmd" "buf.build/go/app/appcmd/appcmdtesting" "buf.build/go/app/appext" "github.com/bufbuild/buf/cmd/buf/internal/internaltesting" "github.com/bufbuild/buf/private/pkg/osext" "github.com/bufbuild/buf/private/pkg/storage" "github.com/bufbuild/buf/private/pkg/storage/storageos" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestConfigMigrateV1DefaultConfig(t *testing.T) { // Cannot be parallel since we chdir. testCompareConfigMigrate(t, "testdata/defaultv1", 0, "") } func TestConfigMigrateV1BetaV1DefaultConfig(t *testing.T) { // Cannot be parallel since we chdir. testCompareConfigMigrate(t, "testdata/defaultv1beta1", 0, "") } func TestConfigMigrateUnknownVersion(t *testing.T) { // Cannot be parallel since we chdir. testCompareConfigMigrate(t, "testdata/unknown", 1, "decode buf.yaml: \"version\" is not set. Please add \"version: v2\"") } func testCompareConfigMigrate(t *testing.T, dir string, expectCode int, expectStderr string) { // Setup temporary bucket with input, then compare it to the output. storageosProvider := storageos.NewProvider() inputBucket, err := storageosProvider.NewReadWriteBucket(filepath.Join(dir, "input")) require.NoError(t, err) tempDir := t.TempDir() tempBucket, err := storageosProvider.NewReadWriteBucket(tempDir) require.NoError(t, err) ctx := t.Context() _, err = storage.Copy(ctx, inputBucket, tempBucket) require.NoError(t, err) var outputBucket storage.ReadWriteBucket if expectCode == 0 { outputBucket, err = storageosProvider.NewReadWriteBucket(filepath.Join(dir, "output")) require.NoError(t, err) } // Run in the temp directory. func() { pwd, err := osext.Getwd() require.NoError(t, err) require.NoError(t, osext.Chdir(tempDir)) defer func() { r := recover() assert.NoError(t, osext.Chdir(pwd)) if r != nil { panic(r) } }() appcmdtesting.Run( t, func(use string) *appcmd.Command { return NewCommand(use, appext.NewBuilder(use)) }, appcmdtesting.WithExpectedExitCode(expectCode), appcmdtesting.WithExpectedStderr(expectStderr), appcmdtesting.WithEnv(internaltesting.NewEnvFunc(t)), ) }() if expectCode != 0 { return // Nothing to compare. } var diff bytes.Buffer require.NoError(t, storage.Diff(ctx, &diff, outputBucket, tempBucket)) assert.Empty(t, diff.String()) } ================================================ FILE: cmd/buf/internal/command/config/configmigrate/testdata/defaultv1/input/buf.yaml ================================================ version: v1 name: "" deps: [] build: excludes: [] lint: use: - STANDARD except: [] ignore: [] ignore_only: {} allow_comment_ignores: false enum_zero_value_suffix: _UNSPECIFIED rpc_allow_same_request_response: false rpc_allow_google_protobuf_empty_requests: false rpc_allow_google_protobuf_empty_responses: false service_suffix: Service breaking: use: - FILE except: [] ignore: [] ignore_only: {} ignore_unstable_packages: false ================================================ FILE: cmd/buf/internal/command/config/configmigrate/testdata/defaultv1/output/buf.yaml ================================================ version: v2 lint: use: - STANDARD except: - FIELD_NOT_REQUIRED - PACKAGE_NO_IMPORT_CYCLE enum_zero_value_suffix: _UNSPECIFIED service_suffix: Service disallow_comment_ignores: true breaking: use: - FILE except: - EXTENSION_NO_DELETE - FIELD_SAME_DEFAULT ================================================ FILE: cmd/buf/internal/command/config/configmigrate/testdata/defaultv1beta1/input/buf.yaml ================================================ version: v1beta1 name: "" deps: [] build: roots: - . excludes: [] lint: use: - STANDARD enum_zero_value_suffix: _UNSPECIFIED rpc_allow_same_request_response: false rpc_allow_google_protobuf_empty_requests: false rpc_allow_google_protobuf_empty_responses: false service_suffix: Service breaking: use: - FILE ================================================ FILE: cmd/buf/internal/command/config/configmigrate/testdata/defaultv1beta1/output/buf.yaml ================================================ version: v2 lint: use: - STANDARD except: - ENUM_FIRST_VALUE_ZERO - FIELD_NOT_REQUIRED - IMPORT_USED - PACKAGE_NO_IMPORT_CYCLE - PROTOVALIDATE - SYNTAX_SPECIFIED enum_zero_value_suffix: _UNSPECIFIED service_suffix: Service disallow_comment_ignores: true breaking: use: - FILE except: - EXTENSION_NO_DELETE - FIELD_SAME_DEFAULT ================================================ FILE: cmd/buf/internal/command/config/configmigrate/testdata/unknown/input/buf.yaml ================================================ name: "empty" ================================================ FILE: cmd/buf/internal/command/config/internal/internal.go ================================================ // Copyright 2020-2026 Buf Technologies, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package internal import ( "context" "errors" "fmt" "io/fs" "buf.build/go/app/appcmd" "buf.build/go/app/appext" "buf.build/go/bufplugin/check" "buf.build/go/standard/xslices" "buf.build/go/standard/xstrings" "github.com/bufbuild/buf/private/buf/bufcli" "github.com/bufbuild/buf/private/buf/bufctl" "github.com/bufbuild/buf/private/bufpkg/bufcheck" "github.com/bufbuild/buf/private/bufpkg/bufconfig" "github.com/bufbuild/buf/private/pkg/normalpath" "github.com/bufbuild/buf/private/pkg/syserror" "github.com/spf13/pflag" ) const ( configuredOnlyFlagName = "configured-only" configFlagName = "config" includeDeprecatedFlagName = "include-deprecated" formatFlagName = "format" versionFlagName = "version" modulePathFlagName = "module-path" ) // NewLSCommand returns a new ls Command. func NewLSCommand( name string, builder appext.SubCommandBuilder, ruleType check.RuleType, ) *appcmd.Command { flags := newFlags() return &appcmd.Command{ Use: name, Short: fmt.Sprintf("List %s rules", ruleType.String()), Args: appcmd.NoArgs, Run: builder.NewRunFunc( func(ctx context.Context, container appext.Container) error { return lsRun( ctx, container, flags, name, ruleType, ) }, ), BindFlags: flags.Bind, } } type flags struct { ConfiguredOnly bool Config string IncludeDeprecated bool Format string Version string ModulePath string } func newFlags() *flags { return &flags{} } func (f *flags) Bind(flagSet *pflag.FlagSet) { flagSet.BoolVar( &f.ConfiguredOnly, configuredOnlyFlagName, false, "List rules that are configured instead of listing all available rules", ) flagSet.StringVar( &f.Config, configFlagName, "", fmt.Sprintf( `The buf.yaml file or data to use for configuration. --%s must be set`, configuredOnlyFlagName, ), ) flagSet.BoolVar( &f.IncludeDeprecated, includeDeprecatedFlagName, false, fmt.Sprintf( `Also print deprecated rules. Has no effect if --%s is set.`, configuredOnlyFlagName, ), ) flagSet.StringVar( &f.Format, formatFlagName, "text", fmt.Sprintf( "The format to print rules as. Must be one of %s", xstrings.SliceToString(bufcli.AllRuleFormatStrings), ), ) flagSet.StringVar( &f.Version, versionFlagName, "", // do not set a default as we need to know if this is unset fmt.Sprintf( "List all the rules for the given configuration version. By default, the version in the buf.yaml in the current directory is used, or the latest version otherwise (currently v2). Cannot be set if --%s is set. Must be one of %s", configuredOnlyFlagName, xslices.Map( bufconfig.AllFileVersions, func(fileVersion bufconfig.FileVersion) string { return fileVersion.String() }, ), ), ) flagSet.StringVar( &f.ModulePath, modulePathFlagName, "", fmt.Sprintf( "The path to the specific module to list configured rules for as specified in the buf.yaml. If the buf.yaml has more than one module defined, this must be set. --%s must be set", configuredOnlyFlagName, ), ) } func lsRun( ctx context.Context, container appext.Container, flags *flags, commandName string, ruleType check.RuleType, ) (retErr error) { if flags.ConfiguredOnly { if flags.Version != "" { return appcmd.NewInvalidArgumentErrorf("--%s cannot be specified if --%s is specified", versionFlagName, configFlagName) } } else { if flags.Config != "" { return appcmd.NewInvalidArgumentErrorf("--%s must be set if --%s is specified", configuredOnlyFlagName, configFlagName) } if flags.ModulePath != "" { return appcmd.NewInvalidArgumentErrorf("--%s must be set if --%s is specified", configuredOnlyFlagName, modulePathFlagName) } } configOverride := flags.Config if flags.Version != "" { configOverride = fmt.Sprintf(`{"version":"%s"}`, flags.Version) } bufYAMLFile, err := bufcli.GetBufYAMLFileForDirPathOrOverride(ctx, ".", configOverride) if err != nil { if !errors.Is(err, fs.ErrNotExist) { return err } bufYAMLFile, err = bufconfig.NewBufYAMLFile( bufconfig.FileVersionV2, []bufconfig.ModuleConfig{ bufconfig.DefaultModuleConfigV2, }, nil, nil, nil, ) if err != nil { return err } } wasmRuntime, err := bufcli.NewWasmRuntime(ctx, container) if err != nil { return err } defer func() { retErr = errors.Join(retErr, wasmRuntime.Close(ctx)) }() controller, err := bufcli.NewController(container) if err != nil { return err } workspace, err := controller.GetWorkspace( ctx, ".", bufctl.WithConfigOverride(configOverride), ) if err != nil { return err } checkClient, err := controller.GetCheckClientForWorkspace( ctx, workspace, wasmRuntime, ) if err != nil { return err } var rules []bufcheck.Rule if flags.ConfiguredOnly { moduleConfigs := bufYAMLFile.ModuleConfigs() var moduleConfig bufconfig.ModuleConfig switch fileVersion := bufYAMLFile.FileVersion(); fileVersion { case bufconfig.FileVersionV1Beta1, bufconfig.FileVersionV1: if len(moduleConfigs) != 1 { return syserror.Newf("got %d ModuleConfigs for a v1beta1/v1 buf.yaml", len(moduleConfigs)) } moduleConfig = moduleConfigs[0] case bufconfig.FileVersionV2: switch len(moduleConfigs) { case 0: return syserror.New("got 0 ModuleConfigs from a BufYAMLFile") case 1: moduleConfig = moduleConfigs[0] default: if flags.ModulePath == "" { return appcmd.NewInvalidArgumentErrorf("--%s must be specified if the the buf.yaml has more than one module", modulePathFlagName) } moduleConfig, err = getModuleConfigForModulePath(moduleConfigs, flags.ModulePath) if err != nil { return err } } default: return syserror.Newf("unknown FileVersion: %v", fileVersion) } var checkConfig bufconfig.CheckConfig // We add all check configs (both lint and breaking) as related configs to check if plugins // have rules configured. // We allocated twice the size of moduleConfigs for both lint and breaking configs. allCheckConfigs := make([]bufconfig.CheckConfig, 0, len(moduleConfigs)*2) for _, moduleConfig := range moduleConfigs { allCheckConfigs = append(allCheckConfigs, moduleConfig.LintConfig()) allCheckConfigs = append(allCheckConfigs, moduleConfig.BreakingConfig()) } switch ruleType { case check.RuleTypeLint: checkConfig = moduleConfig.LintConfig() case check.RuleTypeBreaking: checkConfig = moduleConfig.BreakingConfig() default: return fmt.Errorf("unknown check.RuleType: %v", ruleType) } configuredRuleOptions := []bufcheck.ConfiguredRulesOption{ bufcheck.WithPluginConfigs(bufYAMLFile.PluginConfigs()...), bufcheck.WithPolicyConfigs(bufYAMLFile.PolicyConfigs()...), bufcheck.WithRelatedCheckConfigs(allCheckConfigs...), } rules, err = checkClient.ConfiguredRules( ctx, ruleType, checkConfig, configuredRuleOptions..., ) if err != nil { return err } } else { allRulesOptions := []bufcheck.AllRulesOption{ bufcheck.WithPluginConfigs(bufYAMLFile.PluginConfigs()...), bufcheck.WithPolicyConfigs(bufYAMLFile.PolicyConfigs()...), } rules, err = checkClient.AllRules( ctx, ruleType, bufYAMLFile.FileVersion(), allRulesOptions..., ) if err != nil { return err } } return bufcli.PrintRules( container.Stdout(), rules, flags.Format, flags.IncludeDeprecated, ) } func getModuleConfigForModulePath(moduleConfigs []bufconfig.ModuleConfig, modulePath string) (bufconfig.ModuleConfig, error) { modulePath = normalpath.Normalize(modulePath) // Multiple modules in a v2 workspace may have the same moduleDirPath. moduleConfigsFound := []bufconfig.ModuleConfig{} for _, moduleConfig := range moduleConfigs { if moduleConfig.DirPath() == modulePath { moduleConfigsFound = append(moduleConfigsFound, moduleConfig) } } switch len(moduleConfigsFound) { case 0: return nil, fmt.Errorf("no module found for path %q", modulePath) case 1: return moduleConfigsFound[0], nil default: // TODO: add --module-name flag to allow differentiation return nil, fmt.Errorf("multiple modules found for %q", modulePath) } } ================================================ FILE: cmd/buf/internal/command/convert/convert.go ================================================ // Copyright 2020-2026 Buf Technologies, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package convert import ( "context" "errors" "fmt" "log/slog" "buf.build/go/app/appcmd" "buf.build/go/app/appext" "buf.build/go/standard/xstrings" "github.com/bufbuild/buf/private/buf/bufcli" "github.com/bufbuild/buf/private/buf/bufconvert" "github.com/bufbuild/buf/private/buf/bufctl" "github.com/bufbuild/buf/private/buf/buffetch" "github.com/bufbuild/buf/private/bufpkg/bufanalysis" "github.com/bufbuild/buf/private/bufpkg/bufimage" "github.com/bufbuild/buf/private/bufpkg/bufimage/bufimageutil" "github.com/bufbuild/buf/private/bufpkg/bufmodule" "github.com/bufbuild/buf/private/gen/data/datawkt" "github.com/spf13/pflag" ) const ( errorFormatFlagName = "error-format" typeFlagName = "type" fromFlagName = "from" toFlagName = "to" validateFlagName = "validate" disableSymlinksFlagName = "disable-symlinks" ) // NewCommand returns a new Command. func NewCommand( name string, builder appext.SubCommandBuilder, ) *appcmd.Command { flags := newFlags() return &appcmd.Command{ Use: name + " ", Short: "Convert a message between binary, text, or JSON", Long: ` Use an input proto to interpret a proto/json message and convert it to a different format. Examples: $ buf convert --type= --from= --to= The can be a local .proto file, binary output of "buf build", bsr module or local buf module: $ buf convert example.proto --type=Foo.proto --from=payload.json --to=output.binpb All of , "--from" and "to" accept formatting options: $ buf convert example.proto#format=binpb --type=buf.Foo --from=payload#format=json --to=out#format=json Both and "--from" accept stdin redirecting: $ buf convert <(buf build -o -)#format=binpb --type=foo.Bar --from=<(echo "{\"one\":\"55\"}")#format=json Redirect from stdin to --from: $ echo "{\"one\":\"55\"}" | buf convert buf.proto --type buf.Foo --from -#format=json Redirect from stdin to : $ buf build -o - | buf convert -#format=binpb --type buf.Foo --from=payload.json Use a module on the bsr: $ buf convert --type buf.Foo --from=payload.json `, Args: appcmd.MaximumNArgs(1), Run: builder.NewRunFunc( func(ctx context.Context, container appext.Container) error { return run(ctx, container, flags) }, ), BindFlags: flags.Bind, } } type flags struct { ErrorFormat string Type string From string To string Validate bool DisableSymlinks bool // special InputHashtag string } func newFlags() *flags { return &flags{} } func (f *flags) Bind(flagSet *pflag.FlagSet) { bufcli.BindInputHashtag(flagSet, &f.InputHashtag) bufcli.BindDisableSymlinks(flagSet, &f.DisableSymlinks, disableSymlinksFlagName) flagSet.StringVar( &f.ErrorFormat, errorFormatFlagName, "text", fmt.Sprintf( "The format for build errors printed to stderr. Must be one of %s", xstrings.SliceToString(bufanalysis.AllFormatStrings), ), ) flagSet.StringVar( &f.Type, typeFlagName, "", `The full type name of the message within the input (e.g. acme.weather.v1.Units)`, ) flagSet.StringVar( &f.From, fromFlagName, "-", fmt.Sprintf( `The location of the payload to be converted. Supported formats are %s`, buffetch.MessageFormatsString, ), ) flagSet.StringVar( &f.To, toFlagName, "-", fmt.Sprintf( `The output location of the conversion. Supported formats are %s`, buffetch.MessageFormatsString, ), ) flagSet.BoolVar( &f.Validate, validateFlagName, false, fmt.Sprintf( `Validate the message specified with --%s by applying protovalidate rules to it. See https://github.com/bufbuild/protovalidate for more details.`, fromFlagName, ), ) } func run( ctx context.Context, container appext.Container, flags *flags, ) error { input, err := bufcli.GetInputValue(container, flags.InputHashtag, ".") if err != nil { return err } controller, err := bufcli.NewController( container, bufctl.WithDisableSymlinks(flags.DisableSymlinks), bufctl.WithFileAnnotationErrorFormat(flags.ErrorFormat), ) if err != nil { return err } schemaImage, schemaImageErr := controller.GetImage( ctx, input, ) var resolveWellKnownType bool // only resolve wkts if input was not set. if container.NumArgs() == 0 { if schemaImageErr != nil { resolveWellKnownType = true } if schemaImage != nil { _, filterErr := bufimageutil.FilterImage( schemaImage, bufimageutil.WithIncludeTypes(flags.Type), bufimageutil.WithMutateInPlace(), ) if errors.Is(filterErr, bufimageutil.ErrImageFilterTypeNotFound) { resolveWellKnownType = true } } } if resolveWellKnownType { if _, ok := datawkt.MessageFilePath(flags.Type); ok { var wktErr error schemaImage, wktErr = wellKnownTypeImage( ctx, container.Logger(), flags.Type, ) if wktErr != nil { return wktErr } } } if schemaImageErr != nil && schemaImage == nil { return schemaImageErr } // We can't correctly convert anything that uses message-set wire // format. So we prevent that by having the resolver return an error // if asked to resolve any type that uses it. schemaImage = bufconvert.ImageWithoutMessageSetWireFormatResolution(schemaImage) var fromFunctionOptions []bufctl.FunctionOption if flags.Validate { fromFunctionOptions = append(fromFunctionOptions, bufctl.WithMessageValidation()) } fromMessage, fromMessageEncoding, err := controller.GetMessage( ctx, schemaImage, flags.From, flags.Type, buffetch.MessageEncodingBinpb, fromFunctionOptions..., ) if err != nil { return fmt.Errorf("--%s: %w", fromFlagName, err) } defaultToMessageEncoding, err := inverseEncoding(fromMessageEncoding) if err != nil { return err } if err := controller.PutMessage( ctx, schemaImage, flags.To, fromMessage, defaultToMessageEncoding, ); err != nil { return fmt.Errorf("--%s: %w", toFlagName, err) } return nil } // inverseEncoding returns the opposite encoding of the provided encoding, // which will be the default output encoding for a given payload encoding. func inverseEncoding(encoding buffetch.MessageEncoding) (buffetch.MessageEncoding, error) { switch encoding { case buffetch.MessageEncodingBinpb: return buffetch.MessageEncodingJSON, nil case buffetch.MessageEncodingJSON: return buffetch.MessageEncodingBinpb, nil case buffetch.MessageEncodingTxtpb: return buffetch.MessageEncodingBinpb, nil case buffetch.MessageEncodingYAML: return buffetch.MessageEncodingBinpb, nil default: return 0, fmt.Errorf("unknown message encoding %v", encoding) } } // wellKnownTypeImage returns an Image with just the given WKT type name (google.protobuf.Duration for example). func wellKnownTypeImage( ctx context.Context, logger *slog.Logger, wellKnownTypeName string, ) (bufimage.Image, error) { moduleSetBuilder := bufmodule.NewModuleSetBuilder(ctx, logger, bufmodule.NopModuleDataProvider, bufmodule.NopCommitProvider) moduleSetBuilder.AddLocalModule( datawkt.ReadBucket, ".", true, ) moduleSet, err := moduleSetBuilder.Build() if err != nil { return nil, err } image, err := bufimage.BuildImage( ctx, logger, bufmodule.ModuleSetToModuleReadBucketWithOnlyProtoFiles(moduleSet), ) if err != nil { return nil, err } return bufimageutil.FilterImage( image, bufimageutil.WithIncludeTypes(wellKnownTypeName), bufimageutil.WithMutateInPlace(), ) } ================================================ FILE: cmd/buf/internal/command/convert/convert_test.go ================================================ // Copyright 2020-2026 Buf Technologies, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package convert import ( "os" "strings" "testing" "buf.build/go/app/appcmd" "buf.build/go/app/appcmd/appcmdtesting" "buf.build/go/app/appext" "github.com/stretchr/testify/require" ) func TestConvertDefaultInputBin(t *testing.T) { t.Parallel() appcmdtesting.Run( t, testNewCommand, appcmdtesting.WithExpectedExitCode(0), appcmdtesting.WithExpectedStdout(`{"one":"55"}`), appcmdtesting.WithArgs( "--type", "buf.Foo", "--from", "testdata/convert/bin_json/payload.bin", ), ) } func TestConvertDefaultInputBinpb(t *testing.T) { t.Parallel() appcmdtesting.Run( t, testNewCommand, appcmdtesting.WithExpectedExitCode(0), appcmdtesting.WithExpectedStdout(`{"one":"55"}`), appcmdtesting.WithArgs( "--type", "buf.Foo", "--from", "testdata/convert/bin_json/payload.binpb", ), ) } func TestConvertDefaultInputTxtpb(t *testing.T) { t.Parallel() appcmdtesting.Run( t, testNewCommand, appcmdtesting.WithExpectedExitCode(0), appcmdtesting.WithExpectedStdout(`{"one":"55"}`), appcmdtesting.WithArgs( "--type", "buf.Foo", "--from", "testdata/convert/bin_json/payload.txtpb", "--to", "-#format=json", ), ) } func TestConvertDefaultInputYAML(t *testing.T) { t.Parallel() appcmdtesting.Run( t, testNewCommand, appcmdtesting.WithExpectedExitCode(0), appcmdtesting.WithExpectedStdout(`one: "55"`), appcmdtesting.WithArgs( "--type", "buf.Foo", "--from", "testdata/convert/bin_json/payload.txtpb", "--to", "-#format=yaml", ), ) } func TestConvertFromStdinBin(t *testing.T) { t.Parallel() stdin, err := os.Open("testdata/convert/bin_json/payload.bin") require.NoError(t, err) appcmdtesting.Run( t, testNewCommand, appcmdtesting.WithExpectedExitCode(0), appcmdtesting.WithExpectedStdout(`{"one":"55"}`), appcmdtesting.WithStdin(stdin), appcmdtesting.WithArgs( "--type", "buf.Foo", "--from", "-#format=bin", ), ) } func TestConvertFromStdinBinpb(t *testing.T) { t.Parallel() stdin, err := os.Open("testdata/convert/bin_json/payload.binpb") require.NoError(t, err) appcmdtesting.Run( t, testNewCommand, appcmdtesting.WithExpectedExitCode(0), appcmdtesting.WithExpectedStdout(`{"one":"55"}`), appcmdtesting.WithStdin(stdin), appcmdtesting.WithArgs( "--type", "buf.Foo", "--from", "-#format=binpb", ), ) } func TestConvertFromStdinTxtpbJSON(t *testing.T) { t.Parallel() stdin, err := os.Open("testdata/convert/bin_json/payload.txtpb") require.NoError(t, err) appcmdtesting.Run( t, testNewCommand, appcmdtesting.WithExpectedExitCode(0), appcmdtesting.WithExpectedStdout(`{"one":"55"}`), appcmdtesting.WithStdin(stdin), appcmdtesting.WithArgs( "--type", "buf.Foo", "--from", "-#format=txtpb", "--to", "-#format=json", ), ) } func TestConvertFromStdinTxtpbYAML(t *testing.T) { t.Parallel() stdin, err := os.Open("testdata/convert/bin_json/payload.txtpb") require.NoError(t, err) appcmdtesting.Run( t, testNewCommand, appcmdtesting.WithExpectedExitCode(0), appcmdtesting.WithExpectedStdout(`one: "55"`), appcmdtesting.WithStdin(stdin), appcmdtesting.WithArgs( "--type", "buf.Foo", "--from", "-#format=txtpb", "--to", "-#format=yaml", ), ) } func TestConvertDiscardedStdin(t *testing.T) { t.Parallel() appcmdtesting.Run( t, testNewCommand, appcmdtesting.WithExpectedExitCode(0), appcmdtesting.WithExpectedStdout(`{"one":"55"}`), appcmdtesting.WithStdin(strings.NewReader("this should be discarded")), // stdin is discarded if not needed appcmdtesting.WithArgs( "--type", "buf.Foo", "--from", "testdata/convert/bin_json/payload.binpb", ), ) } func TestConvertWKTBin(t *testing.T) { t.Parallel() appcmdtesting.Run( t, testNewCommand, appcmdtesting.WithExpectedExitCode(0), appcmdtesting.WithExpectedStdout(`"3600s"`), appcmdtesting.WithArgs( "--type", "google.protobuf.Duration", "--from", "testdata/convert/bin_json/duration.bin", ), ) } func TestConvertWKTBinpb(t *testing.T) { t.Parallel() appcmdtesting.Run( t, testNewCommand, appcmdtesting.WithExpectedExitCode(0), appcmdtesting.WithExpectedStdout(`"3600s"`), appcmdtesting.WithArgs( "--type", "google.protobuf.Duration", "--from", "testdata/convert/bin_json/duration.binpb", ), ) } func TestConvertWKTTxtpb(t *testing.T) { t.Parallel() appcmdtesting.Run( t, testNewCommand, appcmdtesting.WithExpectedExitCode(0), appcmdtesting.WithExpectedStdout(`"3600s"`), appcmdtesting.WithArgs( "--type", "google.protobuf.Duration", "--from", "testdata/convert/bin_json/duration.txtpb", "--to", "-#format=json", ), ) } func TestConvertWKTYAML(t *testing.T) { t.Parallel() appcmdtesting.Run( t, testNewCommand, appcmdtesting.WithExpectedExitCode(0), appcmdtesting.WithExpectedStdout(`3600s`), appcmdtesting.WithArgs( "--type", "google.protobuf.Duration", "--from", "testdata/convert/bin_json/duration.txtpb", "--to", "-#format=yaml", ), ) } func TestConvertWKTFormatBin(t *testing.T) { t.Parallel() stdin, err := os.Open("testdata/convert/bin_json/duration.bin") require.NoError(t, err) appcmdtesting.Run( t, testNewCommand, appcmdtesting.WithExpectedExitCode(0), appcmdtesting.WithStdin(stdin), appcmdtesting.WithArgs( "--type=google.protobuf.Duration", "--from=testdata/convert/bin_json/duration.json", "--to", "-#format=bin", ), ) } func TestConvertWKTFormatBinYAML(t *testing.T) { t.Parallel() stdin, err := os.Open("testdata/convert/bin_json/duration.bin") require.NoError(t, err) appcmdtesting.Run( t, testNewCommand, appcmdtesting.WithExpectedExitCode(0), appcmdtesting.WithStdin(stdin), appcmdtesting.WithArgs( "--type=google.protobuf.Duration", "--from=testdata/convert/bin_json/duration.yaml", "--to", "-#format=bin", ), ) } func TestConvertWKTFormatBinpb(t *testing.T) { t.Parallel() stdin, err := os.Open("testdata/convert/bin_json/duration.binpb") require.NoError(t, err) appcmdtesting.Run( t, testNewCommand, appcmdtesting.WithExpectedExitCode(0), appcmdtesting.WithStdin(stdin), appcmdtesting.WithArgs( "--type=google.protobuf.Duration", "--from=testdata/convert/bin_json/duration.json", "--to", "-#format=binpb", ), ) } func TestConvertWKTFormatBinpbYAML(t *testing.T) { t.Parallel() stdin, err := os.Open("testdata/convert/bin_json/duration.binpb") require.NoError(t, err) appcmdtesting.Run( t, testNewCommand, appcmdtesting.WithExpectedExitCode(0), appcmdtesting.WithStdin(stdin), appcmdtesting.WithArgs( "--type=google.protobuf.Duration", "--from=testdata/convert/bin_json/duration.yaml", "--to", "-#format=binpb", ), ) } func TestConvertWKTIncorrectInput(t *testing.T) { t.Parallel() appcmdtesting.Run( t, testNewCommand, appcmdtesting.WithExpectedExitCode(1), appcmdtesting.WithExpectedStdout(""), // explicitly check for empty stdout appcmdtesting.WithArgs( "filedoestexist", "--type=google.protobuf.Duration", "--from=testdata/convert/bin_json/duration.json", "--to", "-#format=binpb", ), ) } func TestConvertWKTGoogleFileLocal(t *testing.T) { t.Parallel() appcmdtesting.Run( t, testNewCommand, appcmdtesting.WithExpectedExitCode(1), appcmdtesting.WithExpectedStdout(""), // explicitly check for empty stdout appcmdtesting.WithArgs( "google/protobuf/timestamp.proto", // this file doesn't exist locally "--type=google.protobuf.Duration", "--from=duration.json", "--to", "-#format=binpb", ), ) } func TestConvertWKTLocalWKTExists(t *testing.T) { t.Parallel() expected := `{"name":"blah"}` // valid google.protobuf.Method message stdin := strings.NewReader(expected) appcmdtesting.Run( t, testNewCommand, appcmdtesting.WithExpectedExitCode(0), appcmdtesting.WithExpectedStdout(expected), appcmdtesting.WithStdin(stdin), appcmdtesting.WithArgs( "--type=google.protobuf.Method", "--from=-#format=json", "--to", "-#format=json", ), ) } func TestConvertWKTLocalChanged(t *testing.T) { t.Parallel() expected := `{"notinoriginal":"blah"}` // notinoriginal exists in the local api.proto stdin := strings.NewReader(expected) appcmdtesting.Run( t, testNewCommand, appcmdtesting.WithExpectedExitCode(0), appcmdtesting.WithExpectedStdout(expected), appcmdtesting.WithStdin(stdin), appcmdtesting.WithArgs( "--type=google.protobuf.Method", "--from=-#format=json", "--to", "-#format=json", ), ) } // No idea what this does compared to above function - it was the same name in table tests, // and table tests dont enforce unique test names. func TestConvertWKTLocalChanged2(t *testing.T) { t.Parallel() stdin := strings.NewReader(`{"notinchanged":"blah"}`) // notinchanged does not exist in the local api.proto appcmdtesting.Run( t, testNewCommand, appcmdtesting.WithExpectedExitCode(0), appcmdtesting.WithExpectedStdout("{}"), // we expect empty json because the field doesn't exist in api.proto appcmdtesting.WithStdin(stdin), appcmdtesting.WithArgs( "--type=google.protobuf.Method", "--from=-#format=json", "--to", "-#format=json", ), ) } func TestConvertWKTImport(t *testing.T) { t.Parallel() expected := `{"syntax":"SYNTAX_PROTO3"}` // Syntax is imported into type.proto stdin := strings.NewReader(expected) appcmdtesting.Run( t, testNewCommand, appcmdtesting.WithExpectedExitCode(0), appcmdtesting.WithExpectedStdout(expected), appcmdtesting.WithStdin(stdin), appcmdtesting.WithArgs( "--type=google.protobuf.Type", "--from=-#format=json", "--to", "-#format=json", ), ) } func testNewCommand(use string) *appcmd.Command { return NewCommand("convert", appext.NewBuilder("convert")) } ================================================ FILE: cmd/buf/internal/command/convert/testdata/convert/README.md ================================================ To re-generate the `descriptor.plain.binpb` file, run the following command in the root of the project: ``` echo "{\"one\":\"55\"}" | \ buf convert cmd/buf/testdata/success \ --type buf.Foo \ --from -#format=json \ --to cmd/buf/internal/command/convert/testdata/convert/descriptor.plain.binpb ``` To re-generate the `bin_json/duration.{bin,binpb,json,txtpb}` files, run the following command in the root of the project: ``` for EXT in bin binpb json txtpb do echo "\"3600s\"" | \ buf convert \ --type google.protobuf.Duration \ --from -#format=json \ --to cmd/buf/internal/command/convert/testdata/convert/bin_json/duration.$EXT done ``` To re-generate the `bin_json/image.{bin,binpb,json,txtpb}` files, run the following command in the root of the project: ``` for EXT in bin binpb json txtpb do buf build cmd/buf/internal/command/convert/testdata/convert/bin_json/buf.proto \ --output cmd/buf/internal/command/convert/testdata/convert/bin_json/image.$EXT done ``` To re-generate the `bin_json/payload.{bin,binpb,json,txtpb}` files, run the following command in the root of the project: ``` for EXT in bin binpb json txtpb do echo "{\"one\":\"55\"}" | \ buf convert cmd/buf/internal/command/convert/testdata/convert/bin_json \ --type buf.Foo \ --from -#format=json \ --to cmd/buf/internal/command/convert/testdata/convert/bin_json/payload.$EXT done ``` ================================================ FILE: cmd/buf/internal/command/convert/testdata/convert/bin_json/api.proto ================================================ syntax = "proto3"; package google.protobuf; // Example of if a well known type exists locally and is different to the // baked in well known types message Method { string name = 1; string notinoriginal = 2; } ================================================ FILE: cmd/buf/internal/command/convert/testdata/convert/bin_json/buf.proto ================================================ syntax = "proto3"; package buf; message Foo { int64 one = 1; } ================================================ FILE: cmd/buf/internal/command/convert/testdata/convert/bin_json/buf.yaml ================================================ version: v1 breaking: use: - FILE lint: use: - DEFAULT ================================================ FILE: cmd/buf/internal/command/convert/testdata/convert/bin_json/duration.binpb ================================================  ================================================ FILE: cmd/buf/internal/command/convert/testdata/convert/bin_json/duration.json ================================================ "3600s" ================================================ FILE: cmd/buf/internal/command/convert/testdata/convert/bin_json/duration.txtpb ================================================ seconds: 3600 ================================================ FILE: cmd/buf/internal/command/convert/testdata/convert/bin_json/duration.yaml ================================================ 3600s ================================================ FILE: cmd/buf/internal/command/convert/testdata/convert/bin_json/image.json ================================================ {"file":[{"name":"buf.proto","package":"buf","messageType":[{"name":"Foo","field":[{"name":"one","number":1,"label":"LABEL_OPTIONAL","type":"TYPE_INT64","jsonName":"one"}]}],"sourceCodeInfo":{"location":[{"span":[0,0,6,1]},{"path":[12],"span":[0,0,18]},{"path":[2],"span":[2,0,12]},{"path":[4,0],"span":[4,0,6,1]},{"path":[4,0,1],"span":[4,8,11]},{"path":[4,0,2,0],"span":[5,2,16]},{"path":[4,0,2,0,5],"span":[5,2,7]},{"path":[4,0,2,0,1],"span":[5,8,11]},{"path":[4,0,2,0,3],"span":[5,14,15]}]},"syntax":"proto3","bufExtension":{"isImport":false,"isSyntaxUnspecified":false}}]} ================================================ FILE: cmd/buf/internal/command/convert/testdata/convert/bin_json/image.txtpb ================================================ file: { name: "buf.proto" package: "buf" message_type: { name: "Foo" field: { name: "one" number: 1 label: LABEL_OPTIONAL type: TYPE_INT64 json_name: "one" } } source_code_info: { location: { span: 0 span: 0 span: 6 span: 1 } location: { path: 12 span: 0 span: 0 span: 18 } location: { path: 2 span: 2 span: 0 span: 12 } location: { path: 4 path: 0 span: 4 span: 0 span: 6 span: 1 } location: { path: 4 path: 0 path: 1 span: 4 span: 8 span: 11 } location: { path: 4 path: 0 path: 2 path: 0 span: 5 span: 2 span: 16 } location: { path: 4 path: 0 path: 2 path: 0 path: 5 span: 5 span: 2 span: 7 } location: { path: 4 path: 0 path: 2 path: 0 path: 1 span: 5 span: 8 span: 11 } location: { path: 4 path: 0 path: 2 path: 0 path: 3 span: 5 span: 14 span: 15 } } syntax: "proto3" buf_extension: { is_import: false is_syntax_unspecified: false } } ================================================ FILE: cmd/buf/internal/command/convert/testdata/convert/bin_json/image.yaml ================================================ file: - bufExtension: isImport: false isSyntaxUnspecified: false messageType: - field: - jsonName: one label: LABEL_OPTIONAL name: one number: 1 type: TYPE_INT64 name: Foo name: buf.proto package: buf sourceCodeInfo: location: - span: - 0 - 0 - 6 - 1 - path: - 12 span: - 0 - 0 - 18 - path: - 2 span: - 2 - 0 - 12 - path: - 4 - 0 span: - 4 - 0 - 6 - 1 - path: - 4 - 0 - 1 span: - 4 - 8 - 11 - path: - 4 - 0 - 2 - 0 span: - 5 - 2 - 16 - path: - 4 - 0 - 2 - 0 - 5 span: - 5 - 2 - 7 - path: - 4 - 0 - 2 - 0 - 1 span: - 5 - 8 - 11 - path: - 4 - 0 - 2 - 0 - 3 span: - 5 - 14 - 15 syntax: proto3 ================================================ FILE: cmd/buf/internal/command/convert/testdata/convert/bin_json/payload.binpb ================================================ 7 ================================================ FILE: cmd/buf/internal/command/convert/testdata/convert/bin_json/payload.json ================================================ {"one":"55"} ================================================ FILE: cmd/buf/internal/command/convert/testdata/convert/bin_json/payload.txtpb ================================================ one: 55 ================================================ FILE: cmd/buf/internal/command/convert/testdata/convert/bin_json/payload.yaml ================================================ one: "55" ================================================ FILE: cmd/buf/internal/command/convert/testdata/convert/descriptor.plain.binpb ================================================ 7 ================================================ FILE: cmd/buf/internal/command/curl/curl.go ================================================ // Copyright 2020-2026 Buf Technologies, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package curl import ( "bytes" "context" "crypto/tls" "encoding/base64" "errors" "fmt" "io" "net" "net/http" "net/url" "os" "path/filepath" "slices" "strings" "sync" "time" "buf.build/go/app" "buf.build/go/app/appcmd" "buf.build/go/app/appext" "buf.build/go/standard/xstrings" "connectrpc.com/connect" "github.com/bufbuild/buf/private/buf/bufcli" "github.com/bufbuild/buf/private/buf/bufcurl" "github.com/bufbuild/buf/private/pkg/netrc" "github.com/bufbuild/buf/private/pkg/verbose" "github.com/quic-go/quic-go" "github.com/quic-go/quic-go/http3" "github.com/spf13/pflag" "google.golang.org/protobuf/reflect/protoreflect" ) const ( // Input schema flags schemaFlagName = "schema" // Reflection flags reflectFlagName = "reflect" reflectHeaderFlagName = "reflect-header" reflectProtocolFlagName = "reflect-protocol" // Protocol/transport flags protocolFlagName = "protocol" unixSocketFlagName = "unix-socket" http2PriorKnowledgeFlagName = "http2-prior-knowledge" http3FlagName = "http3" // TLS flags keyFlagName = "key" certFlagName = "cert" certFlagShortName = "E" caCertFlagName = "cacert" serverNameFlagName = "servername" insecureFlagName = "insecure" insecureFlagShortName = "k" // Action flags listServicesFlagName = "list-services" listMethodsFlagName = "list-methods" // Timeout flags noKeepAliveFlagName = "no-keepalive" keepAliveFlagName = "keepalive-time" connectTimeoutFlagName = "connect-timeout" // Header and request body flags userAgentFlagName = "user-agent" userAgentFlagShortName = "A" userFlagName = "user" userFlagShortName = "u" netrcFlagName = "netrc" netrcFlagShortName = "n" netrcFileFlagName = "netrc-file" headerFlagName = "header" headerFlagShortName = "H" dataFlagName = "data" dataFlagShortName = "d" // Output flags outputFlagName = "output" outputFlagShortName = "o" emitDefaultsFlagName = "emit-defaults" verboseFlagName = "verbose" verboseFlagShortName = "v" ) // NewCommand returns a new Command. func NewCommand( name string, builder appext.SubCommandBuilder, ) *appcmd.Command { flags := newFlags() return &appcmd.Command{ Use: name + " ", Short: "Invoke an RPC endpoint, a la 'cURL'", Long: `This command helps you invoke HTTP RPC endpoints on a server that uses gRPC or Connect. By default, server reflection is used, unless the --reflect flag is set to false. Without server reflection, a --schema flag must be provided to indicate the Protobuf schema for the method being invoked. The only positional argument is the URL of the RPC method to invoke. The name of the method to invoke comes from the last two path components of the URL, which should be the fully-qualified service name and method name, respectively. The URL can use either http or https as the scheme. If http is used then HTTP 1.1 will be used unless the --http2-prior-knowledge flag is set. If https is used then HTTP/2 will be preferred during protocol negotiation and HTTP 1.1 used only if the server does not support HTTP/2. The default RPC protocol used will be Connect. To use a different protocol (gRPC or gRPC-Web), use the --protocol flag. Note that the gRPC protocol cannot be used with HTTP 1.1. The input request is specified via the -d or --data flag. If absent, an empty request is sent. If the flag value starts with an at-sign (@), then the rest of the flag value is interpreted as a filename from which to read the request body. If that filename is just a dash (-), then the request body is read from stdin. The request body is a JSON document that contains the JSON formatted request message. If the RPC method being invoked is a client-streaming method, the request body may consist of multiple JSON values, appended to one another. Multiple JSON documents should usually be separated by whitespace, though this is not strictly required unless the request message type has a custom JSON representation that is not a JSON object. Request metadata (i.e. headers) are defined using -H or --header flags. The flag value is in "name: value" format. But if it starts with an at-sign (@), the rest of the value is interpreted as a filename from which headers are read, each on a separate line. If the filename is just a dash (-), then the headers are read from stdin. If headers and the request body are both to be read from the same file (or both read from stdin), the file must include headers first, then a blank line, and then the request body. Examples: Issue a unary RPC to a plain-text (i.e. "h2c") gRPC server, where the schema for the service is in a Buf module in the current directory, using an empty request message: $ buf curl --schema . --protocol grpc --http2-prior-knowledge \ http://localhost:20202/foo.bar.v1.FooService/DoSomething Issue an RPC to a Connect server, where the schema comes from the Buf Schema Registry, using a request that is defined as a command-line argument: $ buf curl --schema buf.build/connectrpc/eliza \ --data '{"name": "Bob Loblaw"}' \ https://demo.connectrpc.com/connectrpc.eliza.v1.ElizaService/Introduce Issue a unary RPC to a server that supports reflection, with verbose output: $ buf curl --data '{"sentence": "I am not feeling well."}' -v \ https://demo.connectrpc.com/connectrpc.eliza.v1.ElizaService/Say Issue a client-streaming RPC to a gRPC-web server that supports reflection, where custom headers and request data are both in a heredoc: $ buf curl --data @- --header @- --protocol grpcweb \ https://demo.connectrpc.com/connectrpc.eliza.v1.ElizaService/Converse \ < arguments to other buf sub-commands such as build and generate. It can indicate a directory, a file, a remote module in the Buf Schema Registry, or even standard in ("-") for feeding an image or file descriptor set to the command in a shell pipeline. If multiple %s flags are present, they will be consulted in order to resolve service and type names. Setting this flags implies --%s=false unless a %s flag is explicitly present. If both %s and %s flags are in use, reflection will be used first and the schemas will be consulted in order thereafter if reflection fails to resolve a schema element.`, schemaFlagName, reflectFlagName, reflectFlagName, schemaFlagName, reflectFlagName, ), ) flagSet.BoolVar( &f.Reflect, reflectFlagName, true, `If true, use server reflection to determine the schema`, ) flagSet.StringSliceVar( &f.ReflectHeaders, reflectHeaderFlagName, nil, fmt.Sprintf(`Request headers to include with reflection requests. This flag may only be used when --%s is also set. This flag may be specified more than once to indicate multiple headers. Each flag value should have the form "name: value". But a special value of '*' may be used to indicate that all normal request headers (from --%s and -%s flags) should also be included with reflection requests. A special value of '@' means to read headers from the file at . If the path is "-" then headers are read from stdin. It is not allowed to indicate a file with the same path as used with the request data flag (--%s or -%s). Furthermore, it is not allowed to indicate stdin if the schema is expected to be provided via stdin as a file descriptor set or image`, reflectFlagName, headerFlagName, headerFlagShortName, dataFlagName, dataFlagShortName, ), ) flagSet.StringVar( &f.ReflectProtocol, reflectProtocolFlagName, "", `The reflection protocol to use for downloading information from the server. This flag may only be used when server reflection is used. By default, this command will try all known reflection protocols from newest to oldest. If this results in a "Not Implemented" error, then older protocols will be used. In practice, this means that "grpc-v1" is tried first, and "grpc-v1alpha" is used if it doesn't work. If newer reflection protocols are introduced, they may be preferred in the absence of this flag being explicitly set to a specific protocol. The valid values for this flag are "grpc-v1" and "grpc-v1alpha". These correspond to services named "grpc.reflection.v1.ServerReflection" and "grpc.reflection.v1alpha.ServerReflection" respectively`, ) flagSet.StringVar( &f.Protocol, protocolFlagName, connect.ProtocolConnect, `The RPC protocol to use. This can be one of "grpc", "grpcweb", or "connect"`, ) flagSet.StringVar( &f.UnixSocket, unixSocketFlagName, "", `The path to a unix socket that will be used instead of opening a TCP socket to the host and port indicated in the URL`, ) flagSet.BoolVar( &f.HTTP2PriorKnowledge, http2PriorKnowledgeFlagName, false, `This flag can be used to indicate that HTTP/2 should be used. Without this, HTTP 1.1 will be used with URLs with an http scheme, and protocol negotiation will be used to choose either HTTP 1.1 or HTTP/2 for URLs with an https scheme. With this flag set, HTTP/2 is always used, even over plain-text.`, ) flagSet.BoolVar( &f.HTTP3, http3FlagName, false, `This flag can be used to indicate that HTTP/3 should be used. Without this, HTTP 1.1 will be used with URLs with an http scheme, and protocol negotiation will be used to choose either HTTP 1.1 or HTTP/2 for URLs with an https scheme. With this flag set, HTTP/3 is always used.`, ) flagSet.BoolVar( &f.NoKeepAlive, noKeepAliveFlagName, false, `By default, connections are created using TCP keepalive. If this flag is present, they will be disabled`, ) flagSet.Float64Var( &f.KeepAliveTimeSeconds, keepAliveFlagName, 60, `The duration, in seconds, between TCP keepalive transmissions`, ) flagSet.Float64Var( &f.ConnectTimeoutSeconds, connectTimeoutFlagName, 0, `The time limit, in seconds, for a connection to be established with the server. There is no limit if this flag is not present`, ) flagSet.StringVar( &f.Key, keyFlagName, "", fmt.Sprintf(`Path to a PEM-encoded X509 private key file, for using client certificates with TLS. This option is only valid when the URL uses the https scheme. A --%s or -%s flag must also be present to provide the certificate and public key that corresponds to the given private key`, certFlagName, certFlagShortName, ), ) flagSet.StringVarP( &f.Cert, certFlagName, certFlagShortName, "", fmt.Sprintf(`Path to a PEM-encoded X509 certificate file, for using client certificates with TLS. This option is only valid when the URL uses the https scheme. A --%s flag must also be present to provide the private key that corresponds to the given certificate`, keyFlagName, ), ) flagSet.StringVar( &f.CACert, caCertFlagName, "", fmt.Sprintf(`Path to a PEM-encoded X509 certificate pool file that contains the set of trusted certificate authorities/issuers. If omitted, the system's default set of trusted certificates are used to verify the server's certificate. This option is only valid when the URL uses the https scheme. It is not applicable if --%s or -%s flag is used`, insecureFlagName, insecureFlagShortName, ), ) flagSet.BoolVarP( &f.Insecure, insecureFlagName, insecureFlagShortName, false, `If set, the TLS connection will be insecure and the server's certificate will NOT be verified. This is generally discouraged. This option is only valid when the URL uses the https scheme`, ) flagSet.StringVar( &f.ServerName, serverNameFlagName, "", `The server name to use in TLS handshakes (for SNI) if the URL scheme is https. If not specified, the default is the origin host in the URL or the value in a "Host" header if one is provided`, ) flagSet.BoolVar( &f.ListServices, listServicesFlagName, false, `When set, the command lists supported services and then exits. If server reflection is used to provide the RPC schema, then the given URL must be a base URL, not including a service or method name. If the schema source is not server reflection, the URL is not used and may be omitted.`, ) flagSet.BoolVar( &f.ListMethods, listMethodsFlagName, false, `When set, the command lists supported methods and then exits. If server reflection is used to provide the RPC schema, then the given URL must be a base URL, not including a service or method name. If the schema source is not server reflection, the URL is not used and may be omitted.`, ) flagSet.StringVarP( &f.UserAgent, userAgentFlagName, userAgentFlagShortName, "", fmt.Sprintf(`The user agent string to send. This is ignored if a --%s or -%s flag is provided that sets a header named 'User-Agent'.`, headerFlagName, headerFlagShortName, ), ) flagSet.StringVarP( &f.User, userFlagName, userFlagShortName, "", fmt.Sprintf(`The user credentials to send, via a basic authorization header. The value should be in the format "username:password". If the value has no colon, it is assumed to just be the username, in which case you will be prompted to enter a password. This overrides the use of a .netrc file. This is ignored if a --%s or -%s flag is provided that sets a header named 'Authorization'.`, headerFlagName, headerFlagShortName, ), ) flagSet.BoolVarP( &f.Netrc, netrcFlagName, netrcFlagShortName, false, fmt.Sprintf(`If true, a file named .netrc in the user's home directory will be examined to find credentials for the request. The credentials will be sent via a basic authorization header. The command will fail if the file does not have an entry for the hostname in the URL. This flag is ignored if a --%s or -%s flag is present. This is ignored if a --%s or -%s flag is provided that sets a header named 'Authorization'.`, userFlagName, userFlagShortName, headerFlagName, headerFlagShortName, ), ) flagSet.StringVar( &f.NetrcFile, netrcFileFlagName, "", fmt.Sprintf(`This is just like use --%s or -%s, except that the named file is used instead of a file named .netrc in the user's home directory. This flag cannot be used with the --%s or -%s flag. This is ignored if a --%s or -%s flag is provided that sets a header named 'Authorization'.`, netrcFlagName, netrcFlagShortName, netrcFlagName, netrcFlagShortName, headerFlagName, headerFlagShortName, ), ) flagSet.StringSliceVarP( &f.Headers, headerFlagName, headerFlagShortName, nil, fmt.Sprintf(`Request headers to include with the RPC invocation. This flag may be specified more than once to indicate multiple headers. Each flag value should have the form "name: value". A special value of '@' means to read headers from the file at . If the path is "-" then headers are read from stdin. If the same file is indicated as used with the request data flag (--%s or -%s), the file must contain all headers, then a blank line, and then the request body. It is not allowed to indicate stdin if the schema is expected to be provided via stdin as a file descriptor set or image`, dataFlagName, dataFlagShortName, ), ) flagSet.StringVarP( &f.Data, dataFlagName, dataFlagShortName, "", fmt.Sprintf(`Request data. This should be zero or more JSON documents, each indicating a request message. For unary RPCs, there should be exactly one JSON document. A special value of '@' means to read the data from the file at . If the path is "-" then the request data is read from stdin. If the same file is indicated as used with the request headers flags (--%s or -%s), the file must contain all headers, then a blank line, and then the request body. It is not allowed to indicate stdin if the schema is expected to be provided via stdin as a file descriptor set or image`, headerFlagName, headerFlagShortName, ), ) flagSet.StringVarP( &f.Output, outputFlagName, outputFlagShortName, "", `Path to output file to create with response data. If absent, response is printed to stdout`, ) flagSet.BoolVar( &f.EmitDefaults, emitDefaultsFlagName, false, `Emit default values for JSON-encoded responses.`, ) flagSet.BoolVarP( &f.Verbose, verboseFlagName, verboseFlagShortName, false, "Turn on verbose mode", ) } func (f *flags) validate(hasURL, isSecure bool) error { if len(f.Schemas) > 0 && f.Reflect && !f.flagSet.Changed(reflectFlagName) { // Reflect just has default value; unset it since we're going to use --schema instead. f.Reflect = false } if !f.Reflect && len(f.Schemas) == 0 { return fmt.Errorf("must specify --%s if --%s is false", schemaFlagName, reflectFlagName) } if !hasURL && ((!f.ListServices && !f.ListMethods) || f.Reflect) { // If we are trying to use reflection for anything or if we are invoking an RPC (which // means we aren't listing services, listing methods, or describing an element), then // a URL is required. return appcmd.NewInvalidArgumentError("URL positional argument is missing") } if f.ListServices && f.ListMethods { return fmt.Errorf("flags --%s and --%s are mutually exclusive", listServicesFlagName, listMethodsFlagName) } if (f.Key != "" || f.Cert != "" || f.CACert != "" || f.ServerName != "" || f.flagSet.Changed(insecureFlagName)) && !isSecure { return fmt.Errorf( "TLS flags (--%s, --%s, --%s, --%s, --%s) should not be used unless URL is secure (https)", keyFlagName, certFlagName, caCertFlagName, insecureFlagName, serverNameFlagName) } if (f.Key != "") != (f.Cert != "") { return fmt.Errorf("if one of --%s or --%s flags is used, both should be used (mutual TLS with a client certificate requires both)", keyFlagName, certFlagName) } if f.Insecure && f.CACert != "" { return fmt.Errorf("if --%s is set, --%s should not be set as it is unused", insecureFlagName, caCertFlagName) } if !isSecure && !f.HTTP2PriorKnowledge && f.Protocol == connect.ProtocolGRPC { return fmt.Errorf("grpc protocol cannot be used with plain-text URLs (http) unless --%s flag is set", http2PriorKnowledgeFlagName) } if !isSecure && f.HTTP3 { return fmt.Errorf("--%s cannot be used with plain-text URLs (http)", http3FlagName) } if f.UnixSocket != "" && f.HTTP3 { return fmt.Errorf("--%s cannot be used with --%s", unixSocketFlagName, http3FlagName) } if f.Netrc && f.NetrcFile != "" { return fmt.Errorf("--%s and --%s flags are mutually exclusive; they may not both be specified", netrcFlagName, netrcFileFlagName) } var schemaIsStdin bool for _, schema := range f.Schemas { isStdin := strings.HasPrefix(schema, "-") if isStdin && schemaIsStdin { // more than one schema argument wants to use stdin return fmt.Errorf("multiple --%s flags indicate the use of stdin which is not allowed", schemaFlagName) } if isStdin { schemaIsStdin = true } } if (len(f.ReflectHeaders) > 0 || f.flagSet.Changed(reflectProtocolFlagName)) && !f.Reflect { return fmt.Errorf( "reflection flags (--%s, --%s) should not be used if --%s is false", reflectHeaderFlagName, reflectProtocolFlagName, reflectFlagName) } if f.Reflect { if !isSecure && !f.HTTP2PriorKnowledge { return fmt.Errorf("--%s cannot be used with plain-text URLs (http) unless --%s flag is set", reflectFlagName, http2PriorKnowledgeFlagName) } if _, err := bufcurl.ParseReflectProtocol(f.ReflectProtocol); err != nil { return fmt.Errorf( "--%s value must be one of %s", reflectProtocolFlagName, xstrings.SliceToHumanStringOrQuoted(bufcurl.AllKnownReflectProtocolStrings), ) } } switch f.Protocol { case connect.ProtocolConnect, connect.ProtocolGRPC, connect.ProtocolGRPCWeb: default: return fmt.Errorf( "--%s value must be one of %q, %q, or %q", protocolFlagName, connect.ProtocolConnect, connect.ProtocolGRPC, connect.ProtocolGRPCWeb) } if f.NoKeepAlive && f.flagSet.Changed(keepAliveFlagName) { return fmt.Errorf("--%s should not be specified if keepalive is disabled", keepAliveFlagName) } if f.KeepAliveTimeSeconds <= 0 { return fmt.Errorf("--%s value must be positive", keepAliveFlagName) } // these two default to zero (which means no timeout in effect) if f.ConnectTimeoutSeconds < 0 || (f.ConnectTimeoutSeconds == 0 && f.flagSet.Changed(connectTimeoutFlagName)) { return fmt.Errorf("--%s value must be positive", connectTimeoutFlagName) } var dataFile string if after, ok := strings.CutPrefix(f.Data, "@"); ok { dataFile = after if dataFile == "" { return fmt.Errorf("--%s value starting with '@' must indicate '-' for stdin or a filename", dataFlagName) } if dataFile == "-" && schemaIsStdin { return fmt.Errorf("--%s and --%s flags cannot both indicate reading from stdin", schemaFlagName, dataFlagName) } } headerFiles := map[string]struct{}{} if err := validateHeaders(f.Headers, headerFlagName, schemaIsStdin, false, headerFiles); err != nil { return err } reflectHeaderFiles := map[string]struct{}{} if err := validateHeaders(f.ReflectHeaders, reflectHeaderFlagName, schemaIsStdin, true, reflectHeaderFiles); err != nil { return err } for file := range reflectHeaderFiles { if file == dataFile { return fmt.Errorf("--%s and --%s flags cannot indicate the same source", dataFlagName, reflectHeaderFlagName) } } return nil } func (f *flags) determineCredentials( ctx context.Context, container app.Container, verbosePrinter verbose.Printer, host string, ) (string, error) { if f.User != "" { // this flag overrides any netrc-related flags parts := strings.SplitN(f.User, ":", 2) username := parts[0] var password string if len(parts) < 2 { var err error password, err = promptForPassword(ctx, container, fmt.Sprintf("Enter host password for user %q:", username)) if err != nil { return "", fmt.Errorf("could not prompt for password: %w", err) } } else { password = parts[1] } return basicAuth(username, password), nil } // process netrc-related flags netrcFile := f.NetrcFile if netrcFile == "" { if !f.Netrc { // no netrc file usage, so no creds return "", nil } var err error netrcFile, err = netrc.GetFilePath(container) if err != nil { return "", fmt.Errorf("could not determine path to .netrc file: %w", err) } } if _, err := os.Stat(netrcFile); err != nil { if errors.Is(err, os.ErrNotExist) { // This mirrors the behavior of curl when a netrc file does not exist ¯\_(ツ)_/¯ verbosePrinter.Printf("* Couldn't find host %s in the file %q; using no credentials", host, netrcFile) return "", nil } if !strings.Contains(err.Error(), netrcFile) { // make sure error message contains path to file return "", fmt.Errorf("could not read file: %s: %w", netrcFile, err) } return "", fmt.Errorf("could not read file: %w", err) } machine, err := netrc.GetMachineForNameAndFilePath(host, netrcFile) if err != nil { return "", fmt.Errorf("could not read file: %s: %w", netrcFile, err) } if machine == nil { // no creds found for this host verbosePrinter.Printf("* Couldn't find host %s in the file %q; using no credentials", host, netrcFile) return "", nil } username := machine.Login() if strings.ContainsRune(username, ':') { return "", fmt.Errorf("invalid credentials found for %s in %s: username %s should not contain colon", host, netrcFile, username) } password := machine.Password() return basicAuth(username, password), nil } func (f *flags) getTLSConfig(authority string, printer verbose.Printer) (*tls.Config, error) { return bufcurl.MakeVerboseTLSConfig(&bufcurl.TLSSettings{ KeyFile: f.Key, CertFile: f.Cert, CACertFile: f.CACert, ServerName: f.ServerName, Insecure: f.Insecure, HTTP2PriorKnowledge: f.HTTP2PriorKnowledge, HTTP3: f.HTTP3, }, authority, printer) } func promptForPassword(ctx context.Context, container app.Container, prompt string) (string, error) { // NB: The comments below and the mechanism of handling I/O async was // copied from the "registry login" command. // If a user sends a SIGINT to buf, the top-level application context is // cancelled and signal masks are reset. However, during an interactive // login the context is not respected; for example, it takes two SIGINTs // to interrupt the process. // Ideally we could just trigger an I/O timeout by setting the deadline on // stdin, but when stdin is connected to a terminal the underlying fd is in // blocking mode making it ineligible. As changing the mode of stdin is // dangerous, this change takes an alternate approach of simply returning // early. // Note that this does not gracefully handle the case where the terminal is // in no-echo mode, as is the case when prompting for a password // interactively. ch := make(chan struct{}) var password string var err error go func() { defer close(ch) password, err = bufcli.PromptUserForPassword(container, prompt) }() select { case <-ch: return password, err case <-ctx.Done(): ctxErr := ctx.Err() // Otherwise we will print "Failure: context canceled". if errors.Is(ctxErr, context.Canceled) { // Otherwise the next terminal line will be on the same line as the // last output from buf. if _, err := fmt.Fprintln(container.Stdout()); err != nil { return "", err } return "", errors.New("interrupted") } return "", ctxErr } } func basicAuth(username, password string) string { var buf bytes.Buffer buf.WriteString(username) buf.WriteByte(':') buf.WriteString(password) return "Basic " + base64.StdEncoding.EncodeToString(buf.Bytes()) } func validateHeaders(flags []string, flagName string, schemaIsStdin bool, allowAsterisk bool, headerFiles map[string]struct{}) error { var hasAsterisk bool for _, header := range flags { switch { case strings.HasPrefix(header, "@"): file := strings.TrimPrefix(header, "@") if _, ok := headerFiles[file]; ok { return fmt.Errorf("multiple --%s values refer to the same file %s", flagName, file) } if file == "" { return fmt.Errorf("--%s value starting with '@' must indicate '-' for stdin or a filename", flagName) } if file == "-" && schemaIsStdin { return fmt.Errorf("--%s and --%s flags cannot both indicate reading from stdin", schemaFlagName, flagName) } headerFiles[file] = struct{}{} case header == "*": if !allowAsterisk { return fmt.Errorf("--%s value '*' is not valid", flagName) } if hasAsterisk { return fmt.Errorf("multiple --%s values both indicate '*'", flagName) } hasAsterisk = true case header == "": return fmt.Errorf("--%s value cannot be blank", flagName) case strings.ContainsRune(header, '\n'): return fmt.Errorf("--%s value cannot contain a newline", flagName) default: parts := strings.SplitN(header, ":", 2) if len(parts) < 2 { return fmt.Errorf("--%s value is a malformed header: %q", flagName, header) } } } return nil } func verifyEndpointURL(urlArg string) (host string, isSecure bool, err error) { endpointURL, err := url.Parse(urlArg) if err != nil { return "", false, fmt.Errorf("%q is not a valid endpoint URL: %w", urlArg, err) } if endpointURL.Scheme != "http" && endpointURL.Scheme != "https" { return "", false, fmt.Errorf("invalid endpoint URL: scheme %q is not supported", endpointURL.Scheme) } return endpointURL.Host, endpointURL.Scheme == "https", nil } func parseEndpointURL(urlArg string) (service, method, baseURL string, err error) { if strings.HasSuffix(urlArg, "/") { return "", "", "", fmt.Errorf("invalid endpoint URL: %q should not end with a slash (/)", urlArg) } parts := strings.Split(urlArg, "/") if len(parts) < 3 || parts[len(parts)-1] == "" || parts[len(parts)-2] == "" { return "", "", "", fmt.Errorf("invalid endpoint URL: %q should end with two non-empty components indicating service and method", urlArg) } service, method = parts[len(parts)-2], parts[len(parts)-1] baseURL = strings.TrimSuffix(urlArg, service+"/"+method) if baseURL == urlArg { // should not be possible due to above checks return "", "", "", fmt.Errorf("failed to extract base URL from %q", urlArg) } return service, method, baseURL, nil } func run(ctx context.Context, container appext.Container, f *flags) (err error) { var urlArg, host string var isSecure bool if container.NumArgs() != 0 { urlArg = container.Arg(0) var err error host, isSecure, err = verifyEndpointURL(urlArg) if err != nil { return err } } if err := f.validate(urlArg != "", isSecure); err != nil { return err } var service, method, baseURL string switch { case f.ListServices || f.ListMethods: baseURL = urlArg default: service, method, baseURL, err = parseEndpointURL(urlArg) } if err != nil { return err } var verbosePrinter verbose.Printer = verbose.NopPrinter if f.Verbose { verbosePrinter = verbose.NewPrinter(container.Stderr(), container.AppName()) } var clientOptions []connect.ClientOption switch f.Protocol { case connect.ProtocolGRPC: clientOptions = []connect.ClientOption{connect.WithGRPC()} case connect.ProtocolGRPCWeb: clientOptions = []connect.ClientOption{connect.WithGRPCWeb()} } if f.Protocol != connect.ProtocolGRPC { // The transport will log trailers to the verbose printer. But if // we're not using standard grpc protocol, trailers are actually encoded // in an end-of-stream message for streaming calls. So this interceptor // will print the trailers for streaming calls when the response stream // is drained. clientOptions = append(clientOptions, connect.WithInterceptors(bufcurl.TraceTrailersInterceptor(verbosePrinter))) } dataSource := "(argument)" var dataFileReference string if after, ok := strings.CutPrefix(f.Data, "@"); ok { dataFileReference = after if dataFileReference == "-" { dataSource = "(stdin)" } else { dataSource = dataFileReference if absFile, err := filepath.Abs(dataFileReference); err == nil { dataFileReference = absFile } } } requestHeaders, dataReader, err := bufcurl.LoadHeaders(f.Headers, dataFileReference, nil) if err != nil { return err } userAgent := f.UserAgent if userAgent == "" { userAgent = bufcurl.DefaultUserAgent(f.Protocol, bufcli.Version) } if len(requestHeaders.Values("user-agent")) == 0 { requestHeaders.Set("user-agent", userAgent) } var basicCreds *string if len(requestHeaders.Values("authorization")) == 0 { creds, err := f.determineCredentials(ctx, container, verbosePrinter, host) if err != nil { return err } if creds != "" { requestHeaders.Set("authorization", creds) } // set this to non-nil so we know we've already determined credentials basicCreds = &creds } if dataReader == nil { if dataFileReference == "-" { dataReader = os.Stdin } else if dataFileReference != "" { f, err := os.Open(dataFileReference) if err != nil { return bufcurl.ErrorHasFilename(err, dataFileReference) } dataReader = f } else if f.Data != "" { dataReader = io.NopCloser(strings.NewReader(f.Data)) } // dataReader is left nil when nothing specified on command-line } defer func() { if dataReader != nil { err = errors.Join(err, dataReader.Close()) } }() makeTransportOnce := sync.OnceValues(func() (connect.HTTPClient, error) { // We do this lazily since some commands don't need a transport, like listing // services and methods and describing elements when the schema source is // something other than server reflection. We memoize the result to use the // same transport for multiple operations where useful (like for both server // reflection and for subsequently invoking an RPC). if urlArg == "" { // This shouldn't be possible since we check in flags.validate, but just in case return nil, errors.New("URL positional argument is missing") } roundTripper, err := makeHTTPRoundTripper(f, isSecure, bufcurl.GetAuthority(host, requestHeaders), verbosePrinter) if err != nil { return nil, err } return bufcurl.NewVerboseHTTPClient(roundTripper, verbosePrinter), nil }) output := container.Stdout() if f.Output != "" { output, err = os.Create(f.Output) if err != nil { return bufcurl.ErrorHasFilename(err, f.Output) } } resolvers := make([]bufcurl.Resolver, 0, len(f.Schemas)+2) if f.Reflect { reflectHeaders, _, err := bufcurl.LoadHeaders(f.ReflectHeaders, "", requestHeaders) if err != nil { return err } if len(reflectHeaders.Values("authorization")) == 0 { var creds string if basicCreds != nil { creds = *basicCreds } else { if creds, err = f.determineCredentials(ctx, container, verbosePrinter, host); err != nil { return err } } if creds != "" { reflectHeaders.Set("authorization", creds) } } if len(reflectHeaders.Values("user-agent")) == 0 { reflectHeaders.Set("user-agent", userAgent) } reflectProtocol, err := bufcurl.ParseReflectProtocol(f.ReflectProtocol) if err != nil { return err } transport, err := makeTransportOnce() if err != nil { return err } res, closeRes := bufcurl.NewServerReflectionResolver(ctx, transport, clientOptions, baseURL, reflectProtocol, reflectHeaders, verbosePrinter) defer closeRes() resolvers = append(resolvers, res) } controller, err := bufcli.NewController(container) if err != nil { return err } for _, schema := range f.Schemas { image, err := controller.GetImage(ctx, schema) if err != nil { return err } resolvers = append(resolvers, bufcurl.ResolverForImage(image)) } // Add a WKT resolver to the end of the end of the list. This is used // for printing a WKT encoded in a "google.protobuf.Any" type as JSON. wktResolver, err := bufcurl.NewWKTResolver(ctx, container.Logger()) if err != nil { return err } res := bufcurl.CombineResolvers(append(resolvers, wktResolver)...) switch { case f.ListServices || f.ListMethods: serviceNames, err := res.ListServices() if err != nil { return err } slices.Sort(serviceNames) for _, serviceName := range serviceNames { if f.ListServices { if _, err := fmt.Fprintf(container.Stdout(), "%s\n", serviceName); err != nil { return err } } else { serviceDescriptor, err := bufcurl.ResolveServiceDescriptor(res, string(serviceName)) if err != nil { return err } methods := serviceDescriptor.Methods() length := methods.Len() methodNames := make([]protoreflect.Name, length) for i := range length { methodNames[i] = methods.Get(i).Name() } slices.Sort(methodNames) for _, methodName := range methodNames { if _, err := fmt.Fprintf(container.Stdout(), "%s/%s\n", serviceName, methodName); err != nil { return err } } } } return nil default: // Invoke RPC methodDescriptor, err := bufcurl.ResolveMethodDescriptor(res, service, method) if err != nil { return err } transport, err := makeTransportOnce() if err != nil { return err } invoker := bufcurl.NewInvoker(container, verbosePrinter, methodDescriptor, res, f.EmitDefaults, transport, clientOptions, urlArg, output) return invoker.Invoke(ctx, dataSource, dataReader, requestHeaders) } } func makeHTTPRoundTripper(f *flags, isSecure bool, authority string, printer verbose.Printer) (http.RoundTripper, error) { if f.HTTP3 { return makeHTTP3RoundTripper(f, authority, printer) } var dialer net.Dialer if f.ConnectTimeoutSeconds != 0 { dialer.Timeout = secondsToDuration(f.ConnectTimeoutSeconds) } if f.NoKeepAlive { dialer.KeepAlive = -1 } else { dialer.KeepAlive = secondsToDuration(f.KeepAliveTimeSeconds) } var dialFunc func(ctx context.Context, network, address string) (net.Conn, error) if f.UnixSocket != "" { dialFunc = func(ctx context.Context, network, addr string) (net.Conn, error) { printer.Printf("* Dialing unix socket %s...", f.UnixSocket) return dialer.DialContext(ctx, "unix", f.UnixSocket) } } else { dialFunc = func(ctx context.Context, network, address string) (net.Conn, error) { printer.Printf("* Dialing (%s) %s...", network, address) conn, err := dialer.DialContext(ctx, network, address) if err != nil { return nil, err } printer.Printf("* Connected to %s", conn.RemoteAddr().String()) return conn, err } } var dialTLSFunc func(ctx context.Context, network, address string) (net.Conn, error) if isSecure { tlsConfig, err := f.getTLSConfig(authority, printer) if err != nil { return nil, err } dialTLSFunc = func(ctx context.Context, network, addr string) (net.Conn, error) { conn, err := dialFunc(ctx, network, addr) if err != nil { return nil, err } printer.Printf("* ALPN: offering %s", strings.Join(tlsConfig.NextProtos, ",")) tlsConn := tls.Client(conn, tlsConfig) if err := tlsConn.HandshakeContext(ctx); err != nil { return nil, err } return tlsConn, nil } } protocols := new(http.Protocols) protocols.SetHTTP1(!f.HTTP2PriorKnowledge) protocols.SetHTTP2(true) protocols.SetUnencryptedHTTP2(f.HTTP2PriorKnowledge && !isSecure) return &http.Transport{ Proxy: http.ProxyFromEnvironment, DialContext: dialFunc, DialTLSContext: dialTLSFunc, ForceAttemptHTTP2: true, MaxIdleConns: 1, Protocols: protocols, }, nil } func makeHTTP3RoundTripper(f *flags, authority string, printer verbose.Printer) (http.RoundTripper, error) { quicCfg := &quic.Config{ KeepAlivePeriod: -1, } if f.ConnectTimeoutSeconds != 0 { quicCfg.HandshakeIdleTimeout = secondsToDuration(f.ConnectTimeoutSeconds) } if !f.NoKeepAlive { quicCfg.KeepAlivePeriod = secondsToDuration(f.KeepAliveTimeSeconds) } tlsConfig, err := f.getTLSConfig(authority, printer) if err != nil { return nil, err } udpConn, err := net.ListenUDP("udp", nil) if err != nil { return nil, err } transport := &quic.Transport{Conn: udpConn} roundTripper := &http3.Transport{ TLSClientConfig: tlsConfig, QUICConfig: quicCfg, Dial: func(ctx context.Context, addr string, tlsCfg *tls.Config, cfg *quic.Config) (*quic.Conn, error) { printer.Printf("* Dialing (udp) %s...", addr) udpAddr, err := net.ResolveUDPAddr("udp", addr) if err != nil { return nil, err } printer.Printf("* ALPN: offering %s", strings.Join(tlsCfg.NextProtos, ",")) conn, err := transport.DialEarly(ctx, udpAddr, tlsCfg, cfg) if err != nil { return nil, err } printer.Printf("* Connected to %s", conn.RemoteAddr().String()) return conn, err }, EnableDatagrams: false, AdditionalSettings: map[uint64]uint64{}, MaxResponseHeaderBytes: 0, DisableCompression: false, } return roundTripper, nil } func secondsToDuration(secs float64) time.Duration { return time.Duration(float64(time.Second) * secs) } ================================================ FILE: cmd/buf/internal/command/dep/depgraph/depgraph.go ================================================ // Copyright 2020-2026 Buf Technologies, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package depgraph import ( "context" "encoding/json" "fmt" "slices" "buf.build/go/app/appcmd" "buf.build/go/app/appext" "buf.build/go/standard/xslices" "buf.build/go/standard/xstrings" "github.com/bufbuild/buf/private/buf/bufcli" "github.com/bufbuild/buf/private/buf/bufctl" "github.com/bufbuild/buf/private/bufpkg/bufanalysis" "github.com/bufbuild/buf/private/bufpkg/bufmodule" "github.com/bufbuild/buf/private/pkg/dag" "github.com/bufbuild/buf/private/pkg/uuidutil" "github.com/google/uuid" "github.com/spf13/pflag" ) const ( errorFormatFlagName = "error-format" disableSymlinksFlagName = "disable-symlinks" formatFlagName = "format" dotFormatString = "dot" jsonFormatString = "json" ) var ( allGraphFormatStrings = []string{ dotFormatString, jsonFormatString, } ) // NewCommand returns a new Command. func NewCommand( name string, builder appext.SubCommandBuilder, ) *appcmd.Command { flags := newFlags() return &appcmd.Command{ Use: name + " ", Short: "Print the dependency graph", Long: `As an example, if module in directory "src/proto" depends on module "buf.build/foo/bar" from the BSR with commit "12345", and "buf.build/foo/bar:12345" depends on module "buf.build/foo/baz" from the BSR with commit "67890", the following will be printed: digraph { "src/proto" -> "buf.build/foo/bar:12345" "buf.build/foo/bar:12345" -> "buf.build/foo/baz:67890" } The actual output may vary between CLI versions and has no stability guarantees, however the output will always be in valid DOT format. If you'd like us to produce an alternative stable format (such as a Protobuf message that we serialize to JSON), let us know! See https://graphviz.org to explore Graphviz and the DOT language. Installation of graphviz will vary by platform, but is easy to install using homebrew: brew install graphviz You can easily visualize a dependency graph using the dot tool: buf dep graph | dot -Tpng >| graph.png && open graph.png ` + bufcli.GetSourceOrModuleLong(`the source or module to print the dependency graph for`), Args: appcmd.MaximumNArgs(1), Run: builder.NewRunFunc( func(ctx context.Context, container appext.Container) error { return run(ctx, container, flags) }, ), BindFlags: flags.Bind, } } type flags struct { ErrorFormat string DisableSymlinks bool // special InputHashtag string Format string } func newFlags() *flags { return &flags{} } func (f *flags) Bind(flagSet *pflag.FlagSet) { bufcli.BindInputHashtag(flagSet, &f.InputHashtag) bufcli.BindDisableSymlinks(flagSet, &f.DisableSymlinks, disableSymlinksFlagName) flagSet.StringVar( &f.ErrorFormat, errorFormatFlagName, "text", fmt.Sprintf( "The format for build errors printed to stderr. Must be one of %s", xstrings.SliceToString(bufanalysis.AllFormatStrings), ), ) flagSet.StringVar( &f.Format, formatFlagName, dotFormatString, fmt.Sprintf( "The format to print graph as. Must be one of %s", xstrings.SliceToString(allGraphFormatStrings), ), ) } func run( ctx context.Context, container appext.Container, flags *flags, ) error { input, err := bufcli.GetInputValue(container, flags.InputHashtag, ".") if err != nil { return err } controller, err := bufcli.NewController( container, bufctl.WithDisableSymlinks(flags.DisableSymlinks), bufctl.WithFileAnnotationErrorFormat(flags.ErrorFormat), ) if err != nil { return err } workspace, err := controller.GetWorkspace(ctx, input) if err != nil { return err } graph, err := bufmodule.ModuleSetToDAG(workspace) if err != nil { return err } var graphString string switch flags.Format { case dotFormatString: dotString, err := graph.DOTString(moduleToString) if err != nil { return err } graphString = dotString case jsonFormatString: // We traverse each module (node) in the graph and populate the deps (outbound nodes). // We keep track of every module we have seen so we can update their d moduleFullNameOrOpaqueIDToExternalModule := make(map[string]externalModule) if err := graph.WalkNodes( func(module bufmodule.Module, _ []bufmodule.Module, deps []bufmodule.Module) error { moduleFullNameOrOpaqueID := moduleFullNameOrOpaqueID(module) // We have already populated this node through deps, we can skip module. if _, ok := moduleFullNameOrOpaqueIDToExternalModule[moduleFullNameOrOpaqueID]; ok { return nil } // We first scaffold a module with no deps populated yet. externalModule, err := externalModuleNoDepsForModule(module) if err != nil { return err } if err := externalModule.addDeps(deps, graph, moduleFullNameOrOpaqueIDToExternalModule, flags); err != nil { return err } // Sort the deps alphabetically before adding our external module. sortExternalModules(externalModule.Deps) moduleFullNameOrOpaqueIDToExternalModule[moduleFullNameOrOpaqueID] = externalModule return nil }, ); err != nil { return err } externalModules := xslices.MapValuesToSlice(moduleFullNameOrOpaqueIDToExternalModule) // Sort all modules alphabetically. sortExternalModules(externalModules) data, err := json.Marshal(externalModules) if err != nil { return err } graphString = string(data) default: return appcmd.NewInvalidArgumentErrorf("invalid value for --%s: %s", formatFlagName, flags.Format) } _, err = fmt.Fprintln(container.Stdout(), graphString) return err } func moduleToString(module bufmodule.Module) string { if moduleFullName := module.FullName(); moduleFullName != nil { commitID := dashlessCommitIDStringForModule(module) if commitID != "" { return moduleFullName.String() + ":" + commitID } return moduleFullName.String() } return module.OpaqueID() } // moduleFullNameOrOpaqueID returns the FullName for a module if available, otherwise // it returns the OpaqueID. func moduleFullNameOrOpaqueID(module bufmodule.Module) string { if moduleFullName := module.FullName(); moduleFullName != nil { return moduleFullName.String() } return module.OpaqueID() } // dashlessCommitIDStringForModule returns the dashless UUID for the commit. If no commit // is set, we return an empty string. func dashlessCommitIDStringForModule(module bufmodule.Module) string { if commitID := module.CommitID(); commitID != uuid.Nil { return uuidutil.ToDashless(commitID) } return "" } type externalModule struct { // FullName if remote, OpaqueID if no FullName Name string `json:"name,omitempty" yaml:"name,omitempty"` // Dashless Commit string `json:"commit,omitempty" yaml:"commit,omitempty"` Digest string `json:"digest,omitempty" yaml:"digest,omitempty"` Deps []externalModule `json:"deps,omitempty" yaml:"deps,omitempty"` Local bool `json:"local,omitempty" yaml:"local,omitempty"` } func (e *externalModule) addDeps( deps []bufmodule.Module, graph *dag.Graph[string, bufmodule.Module], moduleFullNameOrOpaqueIDToExternalModule map[string]externalModule, flags *flags, ) error { for _, dep := range deps { depFullNameOrOpaqueID := moduleFullNameOrOpaqueID(dep) depExternalModule, ok := moduleFullNameOrOpaqueIDToExternalModule[depFullNameOrOpaqueID] if ok { // If this dependency has already been seen, we can simply update our current module // and return early. e.Deps = append(e.Deps, depExternalModule) return nil } // Otherwise, we create a new external module for our direct dependency. However, we do // not add it to our map yet, we only add it once all transitive dependencies have been // handled. depExternalModule, err := externalModuleNoDepsForModule(dep) if err != nil { return err } transitiveDeps, err := graph.OutboundNodes(dep.OpaqueID()) if err != nil { return err } if err := depExternalModule.addDeps(transitiveDeps, graph, moduleFullNameOrOpaqueIDToExternalModule, flags); err != nil { return err } moduleFullNameOrOpaqueIDToExternalModule[depFullNameOrOpaqueID] = depExternalModule e.Deps = append(e.Deps, depExternalModule) } return nil } // externalModuleNoDepsForModule returns an externalModule for the given bufmodule.Module // without populating the deps. This is because we want to populate the deps from the graph, // so we handle it outside of this function. func externalModuleNoDepsForModule(module bufmodule.Module) (externalModule, error) { // We always calculate the b5 digest here, we do not check the digest type that is stored // in buf.lock. digest, err := module.Digest(bufmodule.DigestTypeB5) if err != nil { return externalModule{}, err } return externalModule{ Name: moduleFullNameOrOpaqueID(module), Commit: dashlessCommitIDStringForModule(module), Digest: digest.String(), Local: module.IsLocal(), }, nil } func sortExternalModules(externalModules []externalModule) { slices.SortFunc( externalModules, func(a externalModule, b externalModule) int { if a.Name > b.Name { return 1 } return -1 }, ) } ================================================ FILE: cmd/buf/internal/command/dep/depprune/depprune.go ================================================ // Copyright 2020-2026 Buf Technologies, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package depprune import ( "context" "buf.build/go/app/appcmd" "buf.build/go/app/appext" "github.com/bufbuild/buf/cmd/buf/internal/command/dep/internal" "github.com/bufbuild/buf/private/buf/bufcli" ) // NewCommand returns a new prune Command. func NewCommand( name string, builder appext.SubCommandBuilder, deprecated string, hidden bool, ) *appcmd.Command { return &appcmd.Command{ Use: name + " ", Short: "Prune unused dependencies from a buf.lock", Long: `The first argument is the directory of your buf.yaml configuration file. Defaults to "." if no argument is specified.`, Args: appcmd.MaximumNArgs(1), Deprecated: deprecated, Hidden: hidden, Run: builder.NewRunFunc( func(ctx context.Context, container appext.Container) error { return run(ctx, container) }, ), } } func run( ctx context.Context, container appext.Container, ) error { dirPath := "." if container.NumArgs() > 0 { dirPath = container.Arg(0) } controller, err := bufcli.NewController(container) if err != nil { return err } workspaceDepManager, err := controller.GetWorkspaceDepManager(ctx, dirPath) if err != nil { return err } configuredDepModuleRefs, err := workspaceDepManager.ConfiguredDepModuleRefs(ctx) if err != nil { return err } configuredDepModuleKeys, err := internal.ModuleKeysAndTransitiveDepModuleKeysForModuleRefs( ctx, container, configuredDepModuleRefs, workspaceDepManager.BufLockFileDigestType(), ) if err != nil { return err } return internal.Prune( ctx, container.Logger(), controller, configuredDepModuleKeys, workspaceDepManager, dirPath, ) } ================================================ FILE: cmd/buf/internal/command/dep/depupdate/depupdate.go ================================================ // Copyright 2020-2026 Buf Technologies, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package depupdate import ( "context" "errors" "fmt" "log/slog" "buf.build/go/app/appcmd" "buf.build/go/app/appext" "buf.build/go/standard/xslices" "github.com/bufbuild/buf/cmd/buf/internal/command/dep/internal" "github.com/bufbuild/buf/private/buf/bufcli" "github.com/bufbuild/buf/private/buf/bufctl" "github.com/bufbuild/buf/private/bufpkg/bufmodule" "github.com/bufbuild/buf/private/pkg/syserror" "github.com/spf13/pflag" ) const ( onlyFlagName = "only" ) // NewCommand returns a new update Command. func NewCommand( name string, builder appext.SubCommandBuilder, deprecated string, hidden bool, ) *appcmd.Command { flags := newFlags() return &appcmd.Command{ Use: name + " ", Short: "Update pinned module dependencies in a buf.lock", Long: `Fetch the latest digests for the specified module references in buf.yaml, and write them and their transitive dependencies to buf.lock. The first argument is the directory of the local module to update. Defaults to "." if no argument is specified.`, Args: appcmd.MaximumNArgs(1), Deprecated: deprecated, Hidden: hidden, Run: builder.NewRunFunc( func(ctx context.Context, container appext.Container) error { return run(ctx, container, flags) }, ), BindFlags: flags.Bind, } } type flags struct { Only []string } func newFlags() *flags { return &flags{} } func (f *flags) Bind(flagSet *pflag.FlagSet) { flagSet.StringSliceVar( &f.Only, onlyFlagName, nil, "The name of the dependency to update. When set, only this dependency and its transitive dependencies are updated. May be passed multiple times", ) // TODO FUTURE: implement _ = flagSet.MarkHidden(onlyFlagName) } // run update the buf.lock file for a specific module. func run( ctx context.Context, container appext.Container, flags *flags, ) (retErr error) { dirPath := "." if container.NumArgs() > 0 { dirPath = container.Arg(0) } if len(flags.Only) > 0 { // TODO FUTURE: implement return syserror.Newf("--%s is not implemented", onlyFlagName) } logger := container.Logger() controller, err := bufcli.NewController(container) if err != nil { return err } workspaceDepManager, err := controller.GetWorkspaceDepManager(ctx, dirPath) if err != nil { return err } configuredDepModuleRefs, err := workspaceDepManager.ConfiguredDepModuleRefs(ctx) if err != nil { return err } configuredDepModuleKeys, err := internal.ModuleKeysAndTransitiveDepModuleKeysForModuleRefs( ctx, container, configuredDepModuleRefs, workspaceDepManager.BufLockFileDigestType(), ) if err != nil { return err } logger.DebugContext( ctx, "all deps", slog.Any("deps", xslices.Map(configuredDepModuleKeys, bufmodule.ModuleKey.String)), ) // Store the existing buf.lock data. existingDepModuleKeys, err := workspaceDepManager.ExistingBufLockFileDepModuleKeys(ctx) if err != nil { return err } if configuredDepModuleKeys == nil && existingDepModuleKeys == nil { // No new configured deps were found, and no existing buf.lock deps were found, so there // is nothing to update, we can return here. // This ensures we do not create an empty buf.lock when one did not exist in the first // place and we do not need to go through the entire operation of updating non-existent // deps and building the image for tamper-proofing. logger.Warn(fmt.Sprintf("No configured dependencies were found to update in %q.", dirPath)) return nil } existingRemotePluginKeys, err := workspaceDepManager.ExistingBufLockFileRemotePluginKeys(ctx) if err != nil { return err } existingRemotePolicyKeys, err := workspaceDepManager.ExistingBufLockFileRemotePolicyKeys(ctx) if err != nil { return err } existingPolicyNameToRemotePluginKeys, err := workspaceDepManager.ExistingBufLockFilePolicyNameToRemotePluginKeys(ctx) if err != nil { return err } // We're about to edit the buf.lock file on disk. If we have a subsequent error, // attempt to revert the buf.lock file. // // TODO FUTURE: We should be able to update the buf.lock file in an in-memory bucket, then do the rebuild, // and if the rebuild is successful, then actually write to disk. It shouldn't even be that much work - just // overlay the new buf.lock file in a union bucket. defer func() { if retErr != nil { retErr = errors.Join(retErr, workspaceDepManager.UpdateBufLockFile(ctx, existingDepModuleKeys, existingRemotePluginKeys, existingRemotePolicyKeys, existingPolicyNameToRemotePluginKeys)) } }() // Edit the buf.lock file with the unpruned dependencies. if err := workspaceDepManager.UpdateBufLockFile(ctx, configuredDepModuleKeys, existingRemotePluginKeys, existingRemotePolicyKeys, existingPolicyNameToRemotePluginKeys); err != nil { return err } workspace, err := controller.GetWorkspace(ctx, dirPath, bufctl.WithIgnoreAndDisallowV1BufWorkYAMLs()) if err != nil { return err } // Validate that the workspace builds. // Building also has the side effect of doing tamper-proofing. if _, err := controller.GetImageForWorkspace( ctx, workspace, // This is a performance optimization - we don't need source code info. bufctl.WithImageExcludeSourceInfo(true), ); err != nil { return err } // Log warnings for users on unused configured deps. return internal.LogUnusedConfiguredDepsForWorkspace(workspace, logger) } ================================================ FILE: cmd/buf/internal/command/dep/internal/internal.go ================================================ // Copyright 2020-2026 Buf Technologies, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package internal import ( "context" "fmt" "log/slog" "buf.build/go/app/appext" "buf.build/go/standard/xslices" "github.com/bufbuild/buf/private/buf/bufcli" "github.com/bufbuild/buf/private/buf/bufctl" "github.com/bufbuild/buf/private/buf/bufworkspace" "github.com/bufbuild/buf/private/bufpkg/bufmodule" "github.com/bufbuild/buf/private/bufpkg/bufparse" "github.com/bufbuild/buf/private/pkg/syserror" ) // ModuleKeysAndTransitiveDepModuleKeysForModuleRefs gets the ModuleKeys for the // ModuleRefs, and all the transitive dependencies. func ModuleKeysAndTransitiveDepModuleKeysForModuleRefs( ctx context.Context, container appext.Container, moduleRefs []bufparse.Ref, digestType bufmodule.DigestType, ) ([]bufmodule.ModuleKey, error) { moduleKeyProvider, err := bufcli.NewModuleKeyProvider(container) if err != nil { return nil, err } moduleKeys, err := moduleKeyProvider.GetModuleKeysForModuleRefs( ctx, moduleRefs, digestType, ) if err != nil { return nil, err } return moduleKeysAndTransitiveDepModuleKeysForModuleKeys(ctx, container, moduleKeys) } // Prune prunes the buf.lock. // // Used by dep/mod prune. func Prune( ctx context.Context, logger *slog.Logger, controller bufctl.Controller, // Contains all the Modules and their transitive dependencies based on the buf.yaml. // // All dependencies must be within this group from RemoteDepsForModuleSet. If a dependency // is not within this group, this means it existed in the buf.lock from a previous buf dep update // call, but no longer is a declared remote dependency based on the current buf.yaml. In this // case, we error. // // This list is computed based on the result of ModuleKeysAndTransitiveDepModuleKeysForModuleRefs. bufYAMLBasedDepModuleKeys []bufmodule.ModuleKey, workspaceDepManager bufworkspace.WorkspaceDepManager, dirPath string, ) error { workspace, err := controller.GetWorkspace(ctx, dirPath, bufctl.WithIgnoreAndDisallowV1BufWorkYAMLs()) if err != nil { return err } // Make sure the workspace builds. if _, err := controller.GetImageForWorkspace( ctx, workspace, bufctl.WithImageExcludeSourceInfo(true), ); err != nil { return err } // Compute those dependencies that are in buf.yaml that are not used at all, and warn // about them. if err := LogUnusedConfiguredDepsForWorkspace(workspace, logger); err != nil { return err } // Step that actually computes remote dependencies based on imports. These are all // that is needed for buf.lock. depModules, err := bufmodule.RemoteDepsForModuleSet(workspace) if err != nil { return err } depModuleKeys, err := xslices.MapError( depModules, func(remoteDep bufmodule.RemoteDep) (bufmodule.ModuleKey, error) { return bufmodule.ModuleToModuleKey(remoteDep, workspaceDepManager.BufLockFileDigestType()) }, ) if err != nil { return err } if err := validateModuleKeysContains(bufYAMLBasedDepModuleKeys, depModuleKeys); err != nil { return err } existingRemotePluginKeys, err := workspaceDepManager.ExistingBufLockFileRemotePluginKeys(ctx) if err != nil { return err } existingRemotePolicyKeys, err := workspaceDepManager.ExistingBufLockFileRemotePolicyKeys(ctx) if err != nil { return err } existingPolicyNameToRemotePluginKeys, err := workspaceDepManager.ExistingBufLockFilePolicyNameToRemotePluginKeys(ctx) if err != nil { return err } return workspaceDepManager.UpdateBufLockFile(ctx, depModuleKeys, existingRemotePluginKeys, existingRemotePolicyKeys, existingPolicyNameToRemotePluginKeys) } // LogUnusedConfiguredDepsForWorkspace takes a workspace and logs the unused configured // dependencies as warnings to the user. func LogUnusedConfiguredDepsForWorkspace( workspace bufworkspace.Workspace, logger *slog.Logger, ) error { malformedDeps, err := bufworkspace.MalformedDepsForWorkspace(workspace) if err != nil { return err } for _, malformedDep := range malformedDeps { switch t := malformedDep.Type(); t { case bufworkspace.MalformedDepTypeUnused: logger.Warn(fmt.Sprintf( `Module %[1]s is declared in your buf.yaml deps but is unused. This command only modifies buf.lock files, not buf.yaml files. Please remove %[1]s from your buf.yaml deps if it is not needed.`, malformedDep.ModuleRef().FullName(), )) default: return fmt.Errorf("unknown MalformedDepType: %v", t) } } return nil } // moduleKeysAndTransitiveDepModuleKeysForModuleKeys returns the ModuleKeys // and all the transitive dependencies. func moduleKeysAndTransitiveDepModuleKeysForModuleKeys( ctx context.Context, container appext.Container, moduleKeys []bufmodule.ModuleKey, ) ([]bufmodule.ModuleKey, error) { graphProvider, err := bufcli.NewGraphProvider(container) if err != nil { return nil, err } // Walk the graph to get all ModuleKeys including transitive dependencies. graph, err := graphProvider.GetGraphForModuleKeys(ctx, moduleKeys) if err != nil { return nil, err } var newModuleKeys []bufmodule.ModuleKey if err := graph.WalkNodes( func(moduleKey bufmodule.ModuleKey, _ []bufmodule.ModuleKey, _ []bufmodule.ModuleKey) error { newModuleKeys = append(newModuleKeys, moduleKey) return nil }, ); err != nil { return nil, err } return newModuleKeys, nil } // validateModuleKeysContains validates that containingModuleKeys is a superset of moduleKeys. // // This is used by Prune to validate that bufYAMLBasedDepModuleKeys are a superset of RemoteDepsForModuleSet. // // See comment on Prune. func validateModuleKeysContains(containingModuleKeys []bufmodule.ModuleKey, moduleKeys []bufmodule.ModuleKey) error { containingFullNameStringToModuleKey, err := getFullNameStringToModuleKey(containingModuleKeys) if err != nil { return syserror.Newf("validateModuleKeysContains: containingModuleKeys: %w", err) } moduleFullNameStringToModuleKey, err := getFullNameStringToModuleKey(moduleKeys) if err != nil { return syserror.Newf("validateModuleKeysContains: moduleKeys: %w", err) } for moduleFullNameString := range moduleFullNameStringToModuleKey { if _, ok := containingFullNameStringToModuleKey[moduleFullNameString]; !ok { return fmt.Errorf( `Module %s is detected to be a still-used dependency from your existing buf.lock, but is not a declared dependency in your buf.yaml deps, and is not a transitive dependency of any declared dependency. Add %s to your buf.yaml deps.`, moduleFullNameString, moduleFullNameString, ) } } return nil } // All ModuleKeys are expected to be unique by FullName. func getFullNameStringToModuleKey(moduleKeys []bufmodule.ModuleKey) (map[string]bufmodule.ModuleKey, error) { return xslices.ToUniqueValuesMap( moduleKeys, func(moduleKey bufmodule.ModuleKey) string { return moduleKey.FullName().String() }, ) } ================================================ FILE: cmd/buf/internal/command/export/export.go ================================================ // Copyright 2020-2026 Buf Technologies, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package export import ( "context" "errors" "fmt" "io/fs" "os" "strings" "buf.build/go/app/appcmd" "buf.build/go/app/appext" "github.com/bufbuild/buf/private/buf/bufcli" "github.com/bufbuild/buf/private/buf/bufctl" "github.com/bufbuild/buf/private/bufpkg/bufmodule" "github.com/bufbuild/buf/private/gen/data/datawkt" "github.com/bufbuild/buf/private/pkg/normalpath" "github.com/bufbuild/buf/private/pkg/storage" "github.com/bufbuild/buf/private/pkg/storage/storageos" "github.com/bufbuild/buf/private/pkg/syserror" "github.com/spf13/pflag" ) const ( excludeImportsFlagName = "exclude-imports" pathsFlagName = "path" outputFlagName = "output" outputFlagShortName = "o" configFlagName = "config" excludePathsFlagName = "exclude-path" disableSymlinksFlagName = "disable-symlinks" allFlagName = "all" ) // NewCommand returns a new Command. func NewCommand( name string, builder appext.SubCommandBuilder, ) *appcmd.Command { flags := newFlags() return &appcmd.Command{ Use: name + " ", Short: "Export proto files from one location to another", Long: bufcli.GetSourceOrModuleLong(`the source or module to export`) + ` Examples: Export proto files in to an output directory. $ buf export --output= Export current directory to another local directory. $ buf export . --output= Export the latest remote module to a local directory. $ buf export --output= Export a specific version of a remote module to a local directory. $ buf export --output= Export a git repo to a local directory. $ buf export https://github.com/owner/repository.git --output= `, Args: appcmd.MaximumNArgs(1), Run: builder.NewRunFunc( func(ctx context.Context, container appext.Container) error { return run(ctx, container, flags) }, ), BindFlags: flags.Bind, } } type flags struct { ExcludeImports bool Paths []string Output string Config string ExcludePaths []string DisableSymlinks bool All bool // special InputHashtag string } func newFlags() *flags { return &flags{} } func (f *flags) Bind(flagSet *pflag.FlagSet) { bufcli.BindDisableSymlinks(flagSet, &f.DisableSymlinks, disableSymlinksFlagName) bufcli.BindInputHashtag(flagSet, &f.InputHashtag) bufcli.BindExcludeImports(flagSet, &f.ExcludeImports, excludeImportsFlagName) bufcli.BindPaths(flagSet, &f.Paths, pathsFlagName) bufcli.BindExcludePaths(flagSet, &f.ExcludePaths, excludePathsFlagName) flagSet.StringVarP( &f.Output, outputFlagName, outputFlagShortName, "", `The output directory for exported files`, ) _ = appcmd.MarkFlagRequired(flagSet, outputFlagName) flagSet.StringVar( &f.Config, configFlagName, "", `The buf.yaml file or data to use for configuration`, ) flagSet.BoolVar( &f.All, allFlagName, false, `When set, include any available documentation and license files for the exported input. If the input has more than one module, then the documentation and license file names will be suffixed with the module name.`, ) } func run( ctx context.Context, container appext.Container, flags *flags, ) error { input, err := bufcli.GetInputValue(container, flags.InputHashtag, ".") if err != nil { return err } controller, err := bufcli.NewController( container, bufctl.WithDisableSymlinks(flags.DisableSymlinks), ) if err != nil { return err } workspace, err := controller.GetWorkspace( ctx, input, bufctl.WithTargetPaths(flags.Paths, flags.ExcludePaths), bufctl.WithConfigOverride(flags.Config), ) if err != nil { return err } moduleReadBucket := bufmodule.ModuleSetToModuleReadBucketWithOnlyProtoFiles(workspace) if err := os.MkdirAll(flags.Output, 0755); err != nil { return err } var options []storageos.ProviderOption if !flags.DisableSymlinks { options = append(options, storageos.ProviderWithSymlinks()) } readWriteBucket, err := storageos.NewProvider(options...).NewReadWriteBucket( flags.Output, storageos.ReadWriteBucketWithSymlinksIfSupported(), ) if err != nil { return err } // If the --all flag is set, then we need to pull the non-proto source files, documentation // and license files, for the input, if available. // We only add non-proto source files for target module(s). // // If the input has more than one target module (e.g. a workspace Git input), then we set // an identifier on the file path. // See [getNonProtoFilePath] docs for details on how that is set. if flags.All { seenModuleNamesForDocs := map[string]int{} seenModuleNamesForLicense := map[string]int{} targetModules := bufmodule.ModuleSetTargetModules(workspace) for _, module := range targetModules { docFile, err := bufmodule.GetDocFile(ctx, module) // If the file is not found, then we ignore it. if err != nil && !errors.Is(err, fs.ErrNotExist) { return err } if docFile != nil { docFilePath := docFile.Path() if len(targetModules) > 1 { docFilePath = getNonProtoFilePath(docFilePath, module, seenModuleNamesForDocs) } if err := storage.CopyReader(ctx, readWriteBucket, docFile, docFilePath); err != nil { return errors.Join(err, docFile.Close()) } if err := docFile.Close(); err != nil { return err } } licenseFile, err := bufmodule.GetLicenseFile(ctx, module) // If the file is not found, then we ignore it. if err != nil && !errors.Is(err, fs.ErrNotExist) { return err } if licenseFile != nil { licenseFilePath := licenseFile.Path() if len(targetModules) > 1 { licenseFilePath = getNonProtoFilePath(licenseFilePath, module, seenModuleNamesForLicense) } if err := storage.CopyReader(ctx, readWriteBucket, licenseFile, licenseFilePath); err != nil { return errors.Join(err, licenseFile.Close()) } if err := licenseFile.Close(); err != nil { return err } } } } // In the case where we are excluding imports, we are allowing users to specify an input // that may not have resolved imports (https://github.com/bufbuild/buf/issues/3002). // Thus we do not need to build the image, and instead we can return the non-import files // from the workspace. if flags.ExcludeImports { if err := moduleReadBucket.WalkFileInfos( ctx, func(fileInfo bufmodule.FileInfo) error { moduleFile, err := moduleReadBucket.GetFile(ctx, fileInfo.Path()) if err != nil { return syserror.Wrap(err) } if err := storage.CopyReadObject(ctx, readWriteBucket, moduleFile); err != nil { return errors.Join(err, moduleFile.Close()) } return moduleFile.Close() }, bufmodule.WalkFileInfosWithOnlyTargetFiles(), ); err != nil { return err } return nil } image, err := controller.GetImageForWorkspace( ctx, workspace, bufctl.WithImageExcludeSourceInfo(true), bufctl.WithImageExcludeImports(flags.ExcludeImports), ) if err != nil { return err } imageFiles := image.Files() if len(imageFiles) == 0 { return errors.New("no .proto target files found") } for _, imageFile := range image.Files() { moduleFile, err := moduleReadBucket.GetFile(ctx, imageFile.Path()) if err != nil { if errors.Is(err, fs.ErrNotExist) && datawkt.Exists(imageFile.Path()) { // Images include all imports, including WKTs. WKTs may or may not exist as part of the Workspace. They are implicitly // added to Images if they are not present in a Module or its dependencies. However, we want to make sure that // we still export them if they were part of a Module, or were part of an explicit dependency (for example, // buf.build/protocolbuffers/wellknowntypes). // // This is the only case where a file may exist in the Image but not in the Workspace. Any other case where a file // does not exist is a system error. continue } return syserror.Wrap(err) } if err := storage.CopyReadObject(ctx, readWriteBucket, moduleFile); err != nil { return errors.Join(err, moduleFile.Close()) } if err := moduleFile.Close(); err != nil { return err } } return nil } // This is a helper function that returns the path non-proto source files should be written // to if the --all flag has been set. // // This sets an identifier for the module using the module name, [bufparse.FullName.Name()] // if available, and if not, we use [module.OpaqueID()]. // // e.g. README.foo.md, README.bar.md // // If a module name is repeated, e.g. acme/foo and bufbuild/foo both have the module name // "foo", then we use an incrementing integer based on the order they are seen in the workspace. // // e.g. README.foo.md, README.foo.2.md func getNonProtoFilePath( path string, module bufmodule.Module, seenModuleNamesForPath map[string]int, ) string { moduleIdentifier := module.OpaqueID() if module.FullName() != nil { moduleIdentifier = module.FullName().Name() seenModuleNamesForPath[module.FullName().Name()]++ count := seenModuleNamesForPath[module.FullName().Name()] if count > 1 { moduleIdentifier = fmt.Sprintf("%s.%d", module.FullName().Name(), count) } } return fmt.Sprintf( "%s.%s%s", strings.TrimSuffix(path, normalpath.Ext(path)), moduleIdentifier, normalpath.Ext(path), ) } ================================================ FILE: cmd/buf/internal/command/format/format.go ================================================ // Copyright 2020-2026 Buf Technologies, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package format import ( "bytes" "context" "errors" "fmt" "io" "os" "buf.build/go/app/appcmd" "buf.build/go/app/appext" "buf.build/go/standard/xslices" "buf.build/go/standard/xstrings" "github.com/bufbuild/buf/private/buf/bufcli" "github.com/bufbuild/buf/private/buf/bufctl" "github.com/bufbuild/buf/private/buf/buffetch" "github.com/bufbuild/buf/private/buf/bufformat" "github.com/bufbuild/buf/private/bufpkg/bufanalysis" "github.com/bufbuild/buf/private/bufpkg/bufmodule" "github.com/bufbuild/buf/private/pkg/storage" "github.com/bufbuild/buf/private/pkg/storage/storageos" "github.com/bufbuild/buf/private/pkg/syserror" "github.com/spf13/pflag" ) const ( configFlagName = "config" diffFlagName = "diff" diffFlagShortName = "d" disableSymlinksFlagName = "disable-symlinks" errorFormatFlagName = "error-format" excludePathsFlagName = "exclude-path" exitCodeFlagName = "exit-code" outputFlagName = "output" outputFlagShortName = "o" pathsFlagName = "path" writeFlagName = "write" writeFlagShortName = "w" ) // NewCommand returns a new Command. func NewCommand( name string, builder appext.SubCommandBuilder, ) *appcmd.Command { flags := newFlags() return &appcmd.Command{ Use: name + " ", Short: "Format Protobuf files", Long: ` By default, the source is the current directory and the formatted content is written to stdout. Examples: Write the current directory's formatted content to stdout: $ buf format Most people will want to rewrite the files defined in the current directory in-place with -w: $ buf format -w Display a diff between the original and formatted content with -d Write a diff instead of the formatted file: $ buf format simple/simple.proto -d $ diff -u simple/simple.proto.orig simple/simple.proto --- simple/simple.proto.orig 2022-03-24 09:44:10.000000000 -0700 +++ simple/simple.proto 2022-03-24 09:44:10.000000000 -0700 @@ -2,8 +2,7 @@ package simple; - message Object { - string key = 1; - bytes value = 2; + string key = 1; + bytes value = 2; } Use the --exit-code flag to exit with a non-zero exit code if there is a diff: $ buf format --exit-code $ buf format -w --exit-code $ buf format -d --exit-code Format a file, directory, or module reference by specifying a source e.g. Write the formatted file to stdout: $ buf format simple/simple.proto syntax = "proto3"; package simple; message Object { string key = 1; bytes value = 2; } Write the formatted directory to stdout: $ buf format simple ... Write the formatted module reference to stdout: $ buf format buf.build/acme/petapis ... Write the result to a specified output file or directory with -o e.g. Write the formatted file to another file: $ buf format simple/simple.proto -o simple/simple.formatted.proto Write the formatted directory to another directory, creating it if it doesn't exist: $ buf format proto -o formatted This also works with module references: $ buf format buf.build/acme/weather -o formatted Rewrite the file(s) in-place with -w. e.g. Rewrite a single file in-place: $ buf format simple.proto -w Rewrite an entire directory in-place: $ buf format proto -w Write a diff and rewrite the file(s) in-place: $ buf format simple -d -w $ diff -u simple/simple.proto.orig simple/simple.proto ... The -w and -o flags cannot be used together in a single invocation. `, Args: appcmd.MaximumNArgs(1), Run: builder.NewRunFunc( func(ctx context.Context, container appext.Container) error { return run(ctx, container, flags) }, ), BindFlags: flags.Bind, } } type flags struct { Config string Diff bool DisableSymlinks bool ErrorFormat string ExcludePaths []string ExitCode bool Paths []string Output string Write bool // special InputHashtag string } func newFlags() *flags { return &flags{} } func (f *flags) Bind(flagSet *pflag.FlagSet) { bufcli.BindInputHashtag(flagSet, &f.InputHashtag) bufcli.BindPaths(flagSet, &f.Paths, pathsFlagName) bufcli.BindExcludePaths(flagSet, &f.ExcludePaths, excludePathsFlagName) bufcli.BindDisableSymlinks(flagSet, &f.DisableSymlinks, disableSymlinksFlagName) flagSet.BoolVarP( &f.Diff, diffFlagName, diffFlagShortName, false, "Display diffs instead of rewriting files", ) flagSet.BoolVar( &f.ExitCode, exitCodeFlagName, false, "Exit with a non-zero exit code if files were not already formatted", ) flagSet.BoolVarP( &f.Write, writeFlagName, writeFlagShortName, false, "Rewrite files in-place", ) flagSet.StringVar( &f.ErrorFormat, errorFormatFlagName, "text", fmt.Sprintf( "The format for build errors printed to stderr. Must be one of %s", xstrings.SliceToString(bufanalysis.AllFormatStrings), ), ) flagSet.StringVarP( &f.Output, outputFlagName, outputFlagShortName, "-", fmt.Sprintf( `The output location for the formatted files. Must be one of format %s. If omitted, the result is written to stdout`, buffetch.DirOrProtoFileFormatsString, ), ) flagSet.StringVar( &f.Config, configFlagName, "", `The buf.yaml file or data to use for configuration`, ) } func run( ctx context.Context, container appext.Container, flags *flags, ) (retErr error) { source, err := bufcli.GetInputValue(container, flags.InputHashtag, ".") if err != nil { return err } // We use getDirOrProtoFileRef to see if we have a valid DirOrProtoFileRef, and if so, // whether or not we have IncludePackageFiles Set. // // We abuse ExternalPaths below to say that if flags.Write is set, just write over // the ExternalPath, You can only really use flags.Write if you have a dir // or proto file. So, we abuse getDirOrProtoFileRef to determine if we have a writable source. // if flags.Write is set // // We also want to check that if we have a ProtoFileRef, we don't have IncludePackageFiles // set, regardless of if flags.Write is set. sourceDirOrProtoFileRef, sourceDirOrProtoFileRefErr := getDirOrProtoFileRef(ctx, container, source) if sourceDirOrProtoFileRefErr == nil { if err := validateNoIncludePackageFiles(sourceDirOrProtoFileRef); err != nil { return err } } if flags.Write { if flags.Output != "-" { return appcmd.NewInvalidArgumentErrorf("cannot use --%s when using --%s", outputFlagName, writeFlagName) } // We abuse ExternalPaths below to say that if flags.Write is set, just write over // the ExternalPath. Also, you can only really use flags.Write if you have a dir // or proto file. So, we abuse getDirOrProtoFileRef to determine if we have a writable source. if sourceDirOrProtoFileRefErr != nil { if errors.Is(sourceDirOrProtoFileRefErr, buffetch.ErrModuleFormatDetectedForDirOrProtoFileRef) { return appcmd.NewInvalidArgumentErrorf("invalid input %q when using --%s: must be a directory or proto file", source, writeFlagName) } return appcmd.NewInvalidArgumentErrorf("invalid input %q when using --%s: %v", source, writeFlagName, sourceDirOrProtoFileRefErr) } } dirOrProtoFileRef, err := getDirOrProtoFileRef(ctx, container, flags.Output) if err != nil { if errors.Is(err, buffetch.ErrModuleFormatDetectedForDirOrProtoFileRef) { return appcmd.NewInvalidArgumentErrorf("--%s must be a directory or proto file", outputFlagName) } return err } if err := validateNoIncludePackageFiles(dirOrProtoFileRef); err != nil { return err } controller, err := bufcli.NewController( container, bufctl.WithDisableSymlinks(flags.DisableSymlinks), bufctl.WithFileAnnotationErrorFormat(flags.ErrorFormat), ) if err != nil { return err } workspace, err := controller.GetWorkspace( ctx, source, bufctl.WithTargetPaths(flags.Paths, flags.ExcludePaths), bufctl.WithConfigOverride(flags.Config), ) if err != nil { return err } moduleReadBucket := bufmodule.ModuleReadBucketWithOnlyTargetFiles( // We only want to start with the target Modules. Otherwise, we're going to fetch potential // ModuleDeps that are not targeted, which may result in buf format making remote calls // when all we care to do is format local files. // // We need to make remote Modules even lazier to make sure that buf format is really // not making these remote calls, but this is one component of it. bufmodule.ModuleSetToModuleReadBucketWithOnlyProtoFilesForTargetModules(workspace), ) originalReadBucket := bufmodule.ModuleReadBucketToStorageReadBucket(moduleReadBucket) formattedReadBucket, err := bufformat.FormatBucket(ctx, originalReadBucket) if err != nil { return err } diffBuffer := bytes.NewBuffer(nil) changedPaths, err := storage.DiffWithFilenames( ctx, diffBuffer, originalReadBucket, formattedReadBucket, storage.DiffWithExternalPaths(), // No need to set prefixes as the buckets are from the same location. ) if err != nil { return err } diffExists := diffBuffer.Len() > 0 defer func() { if retErr == nil && flags.ExitCode && diffExists { retErr = bufctl.ErrFileAnnotation } }() if flags.Diff { if diffExists { if _, err := io.Copy(container.Stdout(), diffBuffer); err != nil { return err } } // If we haven't overridden the output flag and haven't set write, we can stop here. if flags.Output == "-" && !flags.Write { return nil } } if flags.Write { changedPathSet := xslices.ToStructMap(changedPaths) return storage.WalkReadObjects( ctx, formattedReadBucket, "", func(readObject storage.ReadObject) error { if _, ok := changedPathSet[readObject.Path()]; !ok { // no change, nothing to re-write return nil } // TODO FUTURE: This is a legacy hack that we shouldn't use. We should not // rely on external paths being writable. // // We do validation above on the flags.Write flag to quasi-ensure that ExternalPath // will be a real externalPath, but it's not great. file, err := os.OpenFile(readObject.ExternalPath(), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644) if err != nil { return err } defer func() { retErr = errors.Join(retErr, file.Close()) }() if _, err := file.ReadFrom(readObject); err != nil { return err } return nil }, ) } // Both flags.Diff and flags.Write not set, do output logic. switch t := dirOrProtoFileRef.(type) { case buffetch.DirRef: if err := writeToDir(ctx, flags.DisableSymlinks, formattedReadBucket, t); err != nil { return err } case buffetch.ProtoFileRef: if err := writeToProtoFile(ctx, container, formattedReadBucket, t); err != nil { return err } default: return syserror.Newf("buffetch ref type must be dir or proto file: %T", dirOrProtoFileRef) } return nil } func writeToDir( ctx context.Context, disableSymlinks bool, formattedReadBucket storage.ReadBucket, dirRef buffetch.DirRef, ) error { if err := createDirIfNotExists(dirRef.DirPath()); err != nil { return err } readWriteBucket, err := newStorageosProvider(disableSymlinks).NewReadWriteBucket( dirRef.DirPath(), storageos.ReadWriteBucketWithSymlinksIfSupported(), ) if err != nil { return err } // We don't copy with ExternalPaths, we use Paths. // This is what we were always doing, including pre-refactor. _, err = storage.Copy( ctx, formattedReadBucket, readWriteBucket, ) return err } func writeToProtoFile( ctx context.Context, container appext.Container, formattedReadBucket storage.ReadBucket, protoFileRef buffetch.ProtoFileRef, ) (retErr error) { writeCloser, err := buffetch.NewProtoFileWriter(container.Logger()).PutProtoFile( ctx, container, protoFileRef, ) if err != nil { return err } defer func() { retErr = errors.Join(retErr, writeCloser.Close()) }() return storage.WalkReadObjects( ctx, formattedReadBucket, "", func(readObject storage.ReadObject) error { data, err := io.ReadAll(readObject) if err != nil { return err } if _, err := writeCloser.Write(data); err != nil { return err } return nil }, ) } func createDirIfNotExists(dirPath string) error { // OK to use os.Stat instead of os.LStat here as this is CLI-only if _, err := os.Stat(dirPath); err != nil { // We don't need to check fileInfo.IsDir() because it's // already handled by the storageosProvider. if os.IsNotExist(err) { if err := os.MkdirAll(dirPath, 0755); err != nil { return err } // We could os.RemoveAll if the overall command exits without error, but we're // not going to, just to be safe. } } return nil } func getDirOrProtoFileRef( ctx context.Context, container appext.Container, value string, ) (buffetch.DirOrProtoFileRef, error) { return buffetch.NewDirOrProtoFileRefParser( container.Logger(), ).GetDirOrProtoFileRef(ctx, value) } func validateNoIncludePackageFiles(dirOrProtoFileRef buffetch.DirOrProtoFileRef) error { if protoFileRef, ok := dirOrProtoFileRef.(buffetch.ProtoFileRef); ok && protoFileRef.IncludePackageFiles() { // We should have a better answer here. Right now, it's // possible that the other files in the same package are defined // in a remote dependency, which makes it impossible to rewrite // in-place. // // In the case that the user uses the -w flag, we'll either need // to return an error, or omit the file that it can't rewrite in-place // (potentially including a debug log). return appcmd.NewInvalidArgumentError("cannot specify include_package_files=true with format") } return nil } func newStorageosProvider(disableSymlinks bool) storageos.Provider { var options []storageos.ProviderOption if !disableSymlinks { options = append(options, storageos.ProviderWithSymlinks()) } return storageos.NewProvider(options...) } ================================================ FILE: cmd/buf/internal/command/generate/generate.go ================================================ // Copyright 2020-2026 Buf Technologies, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package generate import ( "context" "fmt" "log/slog" "os" "path/filepath" "strconv" "strings" "buf.build/go/app/appcmd" "buf.build/go/app/appext" "buf.build/go/standard/xstrings" "github.com/bufbuild/buf/private/buf/bufcli" "github.com/bufbuild/buf/private/buf/bufctl" "github.com/bufbuild/buf/private/buf/bufgen" "github.com/bufbuild/buf/private/bufpkg/bufanalysis" "github.com/bufbuild/buf/private/bufpkg/bufconfig" "github.com/bufbuild/buf/private/bufpkg/bufimage" "github.com/bufbuild/buf/private/pkg/storage/storageos" "github.com/spf13/pflag" ) const ( templateFlagName = "template" baseOutDirPathFlagName = "output" baseOutDirPathFlagShortName = "o" deleteOutsFlagName = "clean" errorFormatFlagName = "error-format" configFlagName = "config" pathsFlagName = "path" includeImportsFlagName = "include-imports" includeWKTFlagName = "include-wkt" excludePathsFlagName = "exclude-path" disableSymlinksFlagName = "disable-symlinks" typeFlagName = "type" typeDeprecatedFlagName = "include-types" excludeTypeFlagName = "exclude-type" ) // NewCommand returns a new Command. func NewCommand( name string, builder appext.SubCommandBuilder, ) *appcmd.Command { flags := newFlags() return &appcmd.Command{ Use: name + " ", Short: "Generate code with protoc plugins", Long: `This command uses a template file of the shape: # buf.gen.yaml # The version of the generation template. # The valid values are v1beta1, v1 and v2. # Required. version: v2 # When clean is set to true, delete the directories, zip files, and/or jar files specified in the # "out" field for all plugins before running code generation. Defaults to false. # Optional. clean: true # The plugins to run. # Required. plugins: # Use the plugin hosted at buf.build/protocolbuffers/go at version v1.28.1. # If version is omitted, uses the latest version of the plugin. # One of "remote", "local" and "protoc_builtin" is required. - remote: buf.build/protocolbuffers/go:v1.28.1 # The relative output directory. # Required. out: gen/go # The revision of the remote plugin to use, a sequence number that Buf # increments when rebuilding or repackaging the plugin. revision: 4 # Any options to provide to the plugin. # This can be either a single string or a list of strings. # Optional. opt: paths=source_relative # Whether to generate code for imported files as well. # Optional. include_imports: false # Whether to generate code for the well-known types. # Optional. include_wkt: false # Include only these types for this plugin. # Optional. types: - "foo.v1.User" # Exclude these types for this plugin. # Optional. exclude_types: - "buf.validate.oneof" - "buf.validate.message" - "buf.validate.field"" # The name of a local plugin if discoverable in "${PATH}" or its path in the file system. - local: protoc-gen-es out: gen/es include_imports: true include_wkt: true # The full invocation of a local plugin can be specified as a list. - local: ["go", "run", "path/to/plugin.go"] out: gen/plugin # The generation strategy to use. There are two options: # # 1. "directory" # # This will result in buf splitting the input files by directory, and making separate plugin # invocations in parallel. This is roughly the concurrent equivalent of: # # for dir in $(find . -name '*.proto' -print0 | xargs -0 -n1 dirname | sort | uniq); do # protoc -I . $(find "${dir}" -name '*.proto') # done # # Almost every Protobuf plugin either requires this, or works with this, # and this is the recommended and default value. # # 2. "all" # # This will result in buf making a single plugin invocation with all input files. # This is roughly the equivalent of: # # protoc -I . $(find . -name '*.proto') # # This is needed for certain plugins that expect all files to be given at once. # This is also the only strategy for remote plugins. # # If omitted, "directory" is used. Most users should not need to set this option. # Optional. strategy: directory # "protoc_builtin" specifies a plugin that comes with protoc, without the "protoc-gen-" prefix. - protoc_builtin: java out: gen/java # Path to protoc. If not specified, the protoc installation in "${PATH}" is used. # Optional. protoc_path: path/to/protoc # Managed mode modifies file options and/or field options on the fly. managed: # Enables managed mode. enabled: true # Each override rule specifies an option, the value for this option and # optionally the files/fields for which the override is applied. # # The accepted file options are: # - java_package # - java_package_prefix # - java_package_suffix # - java_multiple_files # - java_outer_classname # - java_string_check_utf8 # - go_package # - go_package_prefix # - optimize_for # - csharp_namespace # - csharp_namespace_prefix # - ruby_package # - ruby_package_suffix # - objc_class_prefix # - php_namespace # - php_metadata_namespace # - php_metadata_namespace_suffix # - cc_enable_arenas # # An override rule can apply to a field option. # The accepted field options are: # - jstype # # If multiple overrides for the same option apply to a file or field, # the last rule takes effect. # Optional. override: # Sets "go_package_prefix" to "foo/bar/baz" for all files. - file_option: go_package_prefix value: foo/bar/baz # Sets "java_package_prefix" to "net.foo" for files in "buf.build/foo/bar". - file_option: java_package_prefix value: net.foo module: buf.build/foo/bar # Sets "java_package_prefix" to "dev" for "file.proto". # This overrides the value "net.foo" for "file.proto" from the previous rule. - file_option: java_package_prefix value: dev module: buf.build/foo/bar path: file.proto # Sets "go_package" to "x/y/z" for all files in directory "x/y/z". - file_option: go_package value: foo/bar/baz path: x/y/z # Sets a field's "jstype" to "JS_NORMAL". - field_option: jstype value: JS_STRING field: foo.v1.Bar.baz # Disables managed mode under certain conditions. # Takes precedence over "overrides". # Optional. disable: # Do not modify any options for files in this module. - module: buf.build/googleapis/googleapis # Do not modify any options for this file. - module: buf.build/googleapis/googleapis path: foo/bar/file.proto # Do not modify "java_multiple_files" for any file - file_option: java_multiple_files # Do not modify "csharp_namespace" for files in this module. - module: buf.build/acme/weather file_option: csharp_namespace # The inputs to generate code for. # The inputs here are ignored if an input is specified as a command line argument. # Each input is one of "directory", "git_repo", "module", "tarball", "zip_archive", # "proto_file", "binary_image", "json_image", "text_image" and "yaml_image". # Optional. inputs: # The path to a directory. - directory: x/y/z # The URL of a Git repository. - git_repo: https://github.com/acme/weather.git # The branch to clone. # Optional. branch: dev # The subdirectory in the repository to use. # Optional. subdir: proto # How deep of a clone to perform. # Optional. depth: 30 # The URL of a BSR module. - module: buf.build/acme/weather # Only generate code for these types. # Optional. types: - "foo.v1.User" - "foo.v1.UserService" # Exclude these types. # Optional. exclude_types: - "buf.validate" # Only generate code for files in these paths. # If empty, include all paths. paths: - a/b/c - a/b/d # Do not generate code for files in these paths. exclude_paths: - a/b/c/x.proto - a/b/d/y.proto # The URL or path to a tarball. - tarball: a/b/x.tar.gz # The relative path within the archive to use as the base directory. # Optional. subdir: proto # The compression scheme, derived from the file extension if unspecified. # ".tgz" and ".tar.gz" extensions automatically use Gzip. # ".tar.zst" automatically uses Zstandard. # Optional. compression: gzip # Reads at the relative path and strips some number of components. # Optional. strip_components: 2 # The URL or path to a zip archive. - zip_archive: https://github.com/googleapis/googleapis/archive/master.zip # The number of directories to strip. # Optional. strip_components: 1 # The path to a specific proto file. - proto_file: foo/bar/baz.proto # Whether to generate code for files in the same package as well, default to false. # Optional. include_package_files: true # A Buf image in binary format. # Other image formats are "yaml_image", "text_image" and "json_image". - binary_image: image.binpb.gz # The compression scheme of the image file, derived from file extension if unspecified. # Optional. compression: gzip As an example, here's a typical "buf.gen.yaml" go and grpc, assuming "protoc-gen-go" and "protoc-gen-go-grpc" are on your "$PATH": # buf.gen.yaml version: v2 plugins: - local: protoc-gen-go out: gen/go opt: paths=source_relative - local: protoc-gen-go-grpc out: gen/go opt: - paths=source_relative - require_unimplemented_servers=false By default, buf generate will look for a file of this shape named "buf.gen.yaml" in your current directory. This can be thought of as a template for the set of plugins you want to invoke. The first argument is the source, module, or image to generate from. Defaults to "." if no argument is specified. Use buf.gen.yaml as template, current directory as input: $ buf generate Same as the defaults (template of "buf.gen.yaml", current directory as input): $ buf generate --template buf.gen.yaml . The --template flag also takes YAML or JSON data as input, so it can be used without a file: $ buf generate --template '{"version":"v2","plugins":[{"local":"protoc-gen-go","out":"gen/go"}]}' Download the repository and generate code stubs per the bar.yaml template: $ buf generate --template bar.yaml https://github.com/foo/bar.git Generate to the bar/ directory, prepending bar/ to the out directives in the template: $ buf generate --template bar.yaml -o bar https://github.com/foo/bar.git The paths in the template and the -o flag will be interpreted as relative to the current directory, so you can place your template files anywhere. If you only want to generate stubs for a subset of your input, you can do so via the --path. e.g. Only generate for the files in the directories proto/foo and proto/bar: $ buf generate --path proto/foo --path proto/bar Only generate for the files proto/foo/foo.proto and proto/foo/bar.proto: $ buf generate --path proto/foo/foo.proto --path proto/foo/bar.proto Only generate for the files in the directory proto/foo on your git repository: $ buf generate --template buf.gen.yaml https://github.com/foo/bar.git --path proto/foo Note that all paths must be contained within the same module. For example, if you have a module in "proto", you cannot specify "--path proto", however "--path proto/foo" is allowed as "proto/foo" is contained within "proto". Plugins are invoked in the order they are specified in the template, but each plugin has a per-directory parallel invocation, with results from each invocation combined before writing the result. Insertion points are processed in the order the plugins are specified in the template. `, Args: appcmd.MaximumNArgs(1), Run: builder.NewRunFunc( func(ctx context.Context, container appext.Container) error { return run(ctx, container, flags) }, ), BindFlags: flags.Bind, } } type flags struct { Template string BaseOutDirPath string DeleteOuts *bool ErrorFormat string Files []string Config string Paths []string IncludeImportsOverride *bool IncludeWKTOverride *bool ExcludePaths []string DisableSymlinks bool // We may be able to bind two flags to one string slice but I don't // want to find out what will break if we do. Types []string TypesDeprecated []string ExcludeTypes []string // special InputHashtag string } func newFlags() *flags { return &flags{} } func (f *flags) Bind(flagSet *pflag.FlagSet) { bufcli.BindDisableSymlinks(flagSet, &f.DisableSymlinks, disableSymlinksFlagName) bufcli.BindInputHashtag(flagSet, &f.InputHashtag) bufcli.BindPaths(flagSet, &f.Paths, pathsFlagName) bufcli.BindExcludePaths(flagSet, &f.ExcludePaths, excludePathsFlagName) bindBoolPointer( flagSet, includeImportsFlagName, &f.IncludeImportsOverride, "Also generate all imports except for Well-Known Types", ) bindBoolPointer( flagSet, includeWKTFlagName, &f.IncludeWKTOverride, fmt.Sprintf( "Also generate Well-Known Types. Cannot be set to true without setting --%s to true", includeImportsFlagName, ), ) flagSet.StringVar( &f.Template, templateFlagName, "", `The generation template file or data to use. Must be in either YAML or JSON format`, ) flagSet.StringVarP( &f.BaseOutDirPath, baseOutDirPathFlagName, baseOutDirPathFlagShortName, ".", `The base directory to generate to. This is prepended to the out directories in the generation template`, ) bindBoolPointer( flagSet, deleteOutsFlagName, &f.DeleteOuts, `Prior to generation, delete the directories, jar files, or zip files that the plugins will write to. Allows cleaning of existing assets without having to call rm -rf`, ) flagSet.StringVar( &f.ErrorFormat, errorFormatFlagName, "text", fmt.Sprintf( "The format for build errors, printed to stderr. Must be one of %s", xstrings.SliceToString(bufanalysis.AllFormatStrings), ), ) flagSet.StringVar( &f.Config, configFlagName, "", `The buf.yaml file or data to use for configuration`, ) flagSet.StringSliceVar( &f.Types, typeFlagName, nil, "The types (package, message, enum, extension, service, method) that should be included in this image. When specified, the resulting image will only include descriptors to describe the requested types. Flag usage overrides buf.gen.yaml", ) flagSet.StringSliceVar( &f.TypesDeprecated, typeDeprecatedFlagName, nil, "The types (package, message, enum, extension, service, method) that should be included in this image. When specified, the resulting image will only include descriptors to describe the requested types. Flag usage overrides buf.gen.yaml", ) flagSet.StringSliceVar( &f.ExcludeTypes, excludeTypeFlagName, nil, "The types (package, message, enum, extension, service, method) that should be excluded from this image. When specified, the resulting image will omit descriptors for the specified types and remove any references to them, such as fields typed to an excluded message or enum, or custom options tied to an excluded extension. The image is first filtered by the included types, then further reduced by the excluded. Flag usage overrides buf.gen.yaml", ) _ = flagSet.MarkDeprecated(typeDeprecatedFlagName, fmt.Sprintf("use --%s instead", typeFlagName)) _ = flagSet.MarkHidden(typeDeprecatedFlagName) } func run( ctx context.Context, container appext.Container, flags *flags, ) (retErr error) { logger := container.Logger() if flags.IncludeWKTOverride != nil && *flags.IncludeWKTOverride && (flags.IncludeImportsOverride == nil || !*flags.IncludeImportsOverride) { // You need to set --include-imports to true if you set --include-wkt to true, which isn’t great. // The alternative is to have --include-wkt implicitly set --include-imports, but this could be surprising. // Or we could rename --include-wkt to --include-imports-and/with-wkt. But the summary is that the flag // only makes sense in the context of including imports. return appcmd.NewInvalidArgumentErrorf("Cannot set --%s to true without setting --%s to true", includeWKTFlagName, includeImportsFlagName) } input, err := bufcli.GetInputValue(container, flags.InputHashtag, "") if err != nil { return err } var storageosProvider storageos.Provider if flags.DisableSymlinks { storageosProvider = storageos.NewProvider() } else { storageosProvider = storageos.NewProvider(storageos.ProviderWithSymlinks()) } controller, err := bufcli.NewController( container, bufctl.WithDisableSymlinks(flags.DisableSymlinks), bufctl.WithFileAnnotationErrorFormat(flags.ErrorFormat), ) if err != nil { return err } clientConfig, err := bufcli.NewConnectClientConfig(container) if err != nil { return err } bufGenYAMLFile, err := readBufGenYAMLFile(ctx, storageosProvider, flags.Template) if err != nil { return err } images, err := getInputImages( ctx, logger, controller, input, bufGenYAMLFile, flags.Config, flags.Paths, flags.ExcludePaths, append(flags.Types, flags.TypesDeprecated...), flags.ExcludeTypes, ) if err != nil { return err } generateOptions := []bufgen.GenerateOption{ bufgen.GenerateWithBaseOutDirPath(flags.BaseOutDirPath), } if flags.DeleteOuts != nil { generateOptions = append( generateOptions, bufgen.GenerateWithDeleteOuts(*flags.DeleteOuts), ) } if flags.IncludeImportsOverride != nil { generateOptions = append( generateOptions, bufgen.GenerateWithIncludeImportsOverride(*flags.IncludeImportsOverride), ) } if flags.IncludeWKTOverride != nil { generateOptions = append( generateOptions, bufgen.GenerateWithIncludeWellKnownTypesOverride(*flags.IncludeWKTOverride), ) } return bufgen.NewGenerator( logger, storageosProvider, clientConfig, ).Generate( ctx, container, bufGenYAMLFile.GenerateConfig(), images, generateOptions..., ) } func readBufGenYAMLFile( ctx context.Context, storageosProvider storageos.Provider, templatePath string, ) (bufconfig.BufGenYAMLFile, error) { templatePathExtension := filepath.Ext(templatePath) switch { case templatePath == "": bucket, err := storageosProvider.NewReadWriteBucket(".", storageos.ReadWriteBucketWithSymlinksIfSupported()) if err != nil { return nil, err } return bufconfig.GetBufGenYAMLFileForPrefix(ctx, bucket, ".") case templatePathExtension == ".yaml" || templatePathExtension == ".yml" || templatePathExtension == ".json": // We should not read from a bucket at "." because this path can jump context. configFile, err := os.Open(templatePath) if err != nil { return nil, err } defer configFile.Close() return bufconfig.ReadBufGenYAMLFile(configFile) default: return bufconfig.ReadBufGenYAMLFile(strings.NewReader(templatePath)) } } func getInputImages( ctx context.Context, logger *slog.Logger, controller bufctl.Controller, inputSpecified string, bufGenYAMLFile bufconfig.BufGenYAMLFile, moduleConfigOverride string, targetPathsOverride []string, excludePathsOverride []string, includeTypesOverride []string, excludeTypesOverride []string, ) ([]bufimage.Image, error) { // If input is specified on the command line, we use that. If input is not // specified on the command line, use the default input. if inputSpecified != "" || len(bufGenYAMLFile.InputConfigs()) == 0 { input := "." if inputSpecified != "" { input = inputSpecified } var includeTypes []string if typesConfig := bufGenYAMLFile.GenerateConfig().GenerateTypeConfig(); typesConfig != nil { includeTypes = typesConfig.IncludeTypes() } if len(includeTypesOverride) > 0 { includeTypes = includeTypesOverride } var excludeTypes []string if len(excludeTypesOverride) > 0 { excludeTypes = excludeTypesOverride } inputImage, err := controller.GetImage( ctx, input, bufctl.WithConfigOverride(moduleConfigOverride), bufctl.WithTargetPaths(targetPathsOverride, excludePathsOverride), bufctl.WithImageIncludeTypes(includeTypes), bufctl.WithImageExcludeTypes(excludeTypes), ) if err != nil { return nil, err } return []bufimage.Image{inputImage}, nil } var inputImages []bufimage.Image for _, inputConfig := range bufGenYAMLFile.InputConfigs() { targetPaths := inputConfig.TargetPaths() if len(targetPathsOverride) > 0 { targetPaths = targetPathsOverride } excludePaths := inputConfig.ExcludePaths() if len(excludePathsOverride) > 0 { excludePaths = excludePathsOverride } includeTypes := inputConfig.IncludeTypes() if len(includeTypesOverride) > 0 { includeTypes = includeTypesOverride } excludeTypes := inputConfig.ExcludeTypes() if len(excludeTypesOverride) > 0 { excludeTypes = excludeTypesOverride } inputImage, err := controller.GetImageForInputConfig( ctx, inputConfig, bufctl.WithConfigOverride(moduleConfigOverride), bufctl.WithTargetPaths(targetPaths, excludePaths), bufctl.WithImageIncludeTypes(includeTypes), bufctl.WithImageExcludeTypes(excludeTypes), ) if err != nil { return nil, err } inputImages = append(inputImages, inputImage) } return inputImages, nil } // TODO FUTURE: where does this belong? A flagsext package? // value must not be nil. func bindBoolPointer(flagSet *pflag.FlagSet, name string, value **bool, usage string) { flag := flagSet.VarPF( &boolPointerValue{ valuePointer: value, }, name, "", usage, ) flag.NoOptDefVal = "true" } // Implements pflag.Value. type boolPointerValue struct { // This must not be nil at construction time. valuePointer **bool } func (b *boolPointerValue) Type() string { // From the CLI users' perspective, this is just a bool. return "bool" } func (b *boolPointerValue) String() string { if *b.valuePointer == nil { // From the CLI users' perspective, this is just false. return "false" } return strconv.FormatBool(**b.valuePointer) } func (b *boolPointerValue) Set(value string) error { parsedValue, err := strconv.ParseBool(value) if err != nil { return err } *b.valuePointer = &parsedValue return nil } ================================================ FILE: cmd/buf/internal/command/generate/generate_test.go ================================================ // Copyright 2020-2026 Buf Technologies, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package generate import ( "bytes" "context" "encoding/json" "fmt" "io" "io/fs" "os" "path/filepath" "strings" "testing" "buf.build/go/app/appcmd" "buf.build/go/app/appcmd/appcmdtesting" "buf.build/go/app/appext" "buf.build/go/standard/xslices" "buf.build/go/standard/xtesting" "github.com/bufbuild/buf/cmd/buf/internal/internaltesting" "github.com/bufbuild/buf/private/buf/buftesting" "github.com/bufbuild/buf/private/pkg/normalpath" "github.com/bufbuild/buf/private/pkg/storage" "github.com/bufbuild/buf/private/pkg/storage/storagearchive" "github.com/bufbuild/buf/private/pkg/storage/storagemem" "github.com/bufbuild/buf/private/pkg/storage/storageos" "github.com/spf13/pflag" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) // TODO FUTURE: this has to change if we split up this repository var buftestingDirPath = filepath.Join( "..", "..", "..", "..", "..", "..", "private", "buf", "buftesting", ) func TestCompareGeneratedStubsGoogleapisGo(t *testing.T) { xtesting.SkipIfShort(t) t.Parallel() googleapisDirPath := buftesting.GetGoogleapisDirPath(t, buftestingDirPath) testCompareGeneratedStubs( t, googleapisDirPath, []*testPluginInfo{ {name: "go", opt: "Mgoogle/api/auth.proto=foo"}, }, ) } func TestCompareGeneratedStubsGoogleapisGoZip(t *testing.T) { xtesting.SkipIfShort(t) t.Parallel() googleapisDirPath := buftesting.GetGoogleapisDirPath(t, buftestingDirPath) testCompareGeneratedStubsArchive( t, googleapisDirPath, []*testPluginInfo{ {name: "go", opt: "Mgoogle/api/auth.proto=foo"}, }, false, ) } func TestCompareGeneratedStubsGoogleapisGoJar(t *testing.T) { xtesting.SkipIfShort(t) t.Parallel() googleapisDirPath := buftesting.GetGoogleapisDirPath(t, buftestingDirPath) testCompareGeneratedStubsArchive( t, googleapisDirPath, []*testPluginInfo{ {name: "go", opt: "Mgoogle/api/auth.proto=foo"}, }, true, ) } func TestCompareGeneratedStubsGoogleapisObjc(t *testing.T) { xtesting.SkipIfShort(t) t.Parallel() googleapisDirPath := buftesting.GetGoogleapisDirPath(t, buftestingDirPath) testCompareGeneratedStubs( t, googleapisDirPath, []*testPluginInfo{{name: "objc"}}, ) } func TestCompareGeneratedStubsGoogleapisPyi(t *testing.T) { xtesting.SkipIfShort(t) t.Parallel() googleapisDirPath := buftesting.GetGoogleapisDirPath(t, buftestingDirPath) testCompareGeneratedStubs( t, googleapisDirPath, []*testPluginInfo{{name: "pyi"}}, ) } func TestCompareInsertionPointOutput(t *testing.T) { xtesting.SkipIfShort(t) t.Parallel() insertionTestdataDirPath := filepath.Join("testdata", "insertion") testCompareGeneratedStubs( t, insertionTestdataDirPath, []*testPluginInfo{ {name: "insertion-point-receiver"}, {name: "insertion-point-writer"}, }, ) } func TestGenerateV2LocalPluginBasic(t *testing.T) { t.Parallel() tempDirPath := t.TempDir() input := filepath.Join("testdata", "v2", "local_plugin") template := filepath.Join("testdata", "v2", "local_plugin", "buf.basic.gen.yaml") testRunSuccess( t, "--output", tempDirPath, "--template", template, input, ) expected, err := storagemem.NewReadBucket( map[string][]byte{ filepath.Join("gen", "a", "v1", "a.top-level-type-names.yaml"): []byte(`messages: - a.v1.Bar - a.v1.Foo `), filepath.Join("gen", "b", "v1", "b.top-level-type-names.yaml"): []byte(`messages: - b.v1.Bar - b.v1.Foo `), }, ) require.NoError(t, err) actual, err := storageos.NewProvider().NewReadWriteBucket(tempDirPath) require.NoError(t, err) diff, err := storage.DiffBytes(t.Context(), expected, actual) require.NoError(t, err) require.Empty(t, string(diff)) } func TestGenerateV2LocalPluginTypes(t *testing.T) { t.Parallel() testRunTypeArgs := func(t *testing.T, expect map[string][]byte, args ...string) { t.Helper() tempDirPath := t.TempDir() testRunSuccess( t, append([]string{ "--output", tempDirPath, }, args...)..., ) expected, err := storagemem.NewReadBucket(expect) require.NoError(t, err) require.NoError(t, err) actual, err := storageos.NewProvider().NewReadWriteBucket(tempDirPath) require.NoError(t, err) diff, err := storage.DiffBytes(t.Context(), expected, actual) require.NoError(t, err) require.Empty(t, string(diff)) } // buf.basic.gen.yaml testRunTypeArgs(t, map[string][]byte{ filepath.Join("gen", "a", "v1", "a.top-level-type-names.yaml"): []byte(`messages: - a.v1.Bar - a.v1.Foo `), filepath.Join("gen", "b", "v1", "b.top-level-type-names.yaml"): []byte(`messages: - b.v1.Bar - b.v1.Foo `), }, "--template", filepath.Join("testdata", "v2", "local_plugin", "buf.basic.gen.yaml"), filepath.Join("testdata", "v2", "local_plugin"), ) // buf.types.gen.yaml testRunTypeArgs(t, map[string][]byte{ filepath.Join("gen", "a", "v1", "a.top-level-type-names.yaml"): []byte(`messages: - a.v1.Foo `), }, "--template", filepath.Join("testdata", "v2", "local_plugin", "buf.types.gen.yaml"), ) // input specified testRunTypeArgs(t, map[string][]byte{ filepath.Join("gen", "a", "v1", "a.top-level-type-names.yaml"): []byte(`messages: - a.v1.Bar - a.v1.Foo `), filepath.Join("gen", "b", "v1", "b.top-level-type-names.yaml"): []byte(`messages: - b.v1.Bar - b.v1.Foo `), }, "--template", filepath.Join("testdata", "v2", "local_plugin", "buf.types.gen.yaml"), filepath.Join("testdata", "v2", "local_plugin"), // input ) // --template as CLI flag testRunTypeArgs(t, map[string][]byte{ filepath.Join("gen", "a", "v1", "a.top-level-type-names.yaml"): []byte(`messages: - a.v1.Foo `), }, "--template", `version: v2 plugins: - local: protoc-gen-top-level-type-names-yaml out: gen inputs: - directory: ./testdata/v2/local_plugin types: - a.v1.Foo`, ) // --type testRunTypeArgs(t, map[string][]byte{ filepath.Join("gen", "b", "v1", "b.top-level-type-names.yaml"): []byte(`messages: - b.v1.Bar `), }, "--template", filepath.Join("testdata", "v2", "local_plugin", "buf.types.gen.yaml"), "--type", "b.v1.Bar", filepath.Join("testdata", "v2", "local_plugin"), ) // --path testRunTypeArgs(t, map[string][]byte{ filepath.Join("gen", "b", "v1", "b.top-level-type-names.yaml"): []byte(`messages: - b.v1.Bar - b.v1.Foo `), }, "--template", filepath.Join("testdata", "v2", "local_plugin", "buf.types.gen.yaml"), "--path", filepath.Join("testdata", "v2", "local_plugin", "b"), filepath.Join("testdata", "v2", "local_plugin"), ) // --exclude-path testRunTypeArgs(t, map[string][]byte{ filepath.Join("gen", "a", "v1", "a.top-level-type-names.yaml"): []byte(`messages: - a.v1.Bar - a.v1.Foo `), }, "--template", filepath.Join("testdata", "v2", "local_plugin", "buf.types.gen.yaml"), "--exclude-path", filepath.Join("testdata", "v2", "local_plugin", "b", "v1"), filepath.Join("testdata", "v2", "local_plugin"), ) // buf.paths.gen.yaml testRunTypeArgs(t, map[string][]byte{ filepath.Join("gen", "a", "v1", "a.top-level-type-names.yaml"): []byte(`messages: - a.v1.Bar - a.v1.Foo `), }, "--template", filepath.Join("testdata", "v2", "local_plugin", "buf.paths.gen.yaml"), ) // buf.exclude.paths.gen.yaml testRunTypeArgs(t, map[string][]byte{ filepath.Join("gen", "b", "v1", "b.top-level-type-names.yaml"): []byte(`messages: - b.v1.Bar - b.v1.Foo `), }, "--template", filepath.Join("testdata", "v2", "local_plugin", "buf.exclude.paths.gen.yaml"), ) // --type overrides template testRunTypeArgs(t, map[string][]byte{ filepath.Join("gen", "b", "v1", "b.top-level-type-names.yaml"): []byte(`messages: - b.v1.Bar `), }, "--template", filepath.Join("testdata", "v2", "local_plugin", "buf.types.gen.yaml"), "--type", "b.v1.Bar", ) // buf.gen.yaml has types and exclude_types on inputs and plugins testRunTypeArgs(t, map[string][]byte{ filepath.Join("gen", "a", "v1", "a.top-level-type-names.yaml"): []byte(`messages: - a.v1.Foo `), filepath.Join("gen", "b", "v1", "b.top-level-type-names.yaml"): []byte(`messages: - b.v1.Baz `), }, "--template", filepath.Join("testdata", "v2", "types", "buf.gen.yaml"), ) // --exclude-type override testRunTypeArgs(t, map[string][]byte{ filepath.Join("gen", "a", "v1", "a.top-level-type-names.yaml"): []byte(`messages: - a.v1.Foo `), }, "--template", filepath.Join("testdata", "v2", "types", "buf.gen.yaml"), "--exclude-type", "b.v1.Baz", ) } func TestOutputFlag(t *testing.T) { t.Parallel() for _, paths := range []struct { template string dir string }{ // v1 buf.gen.yaml, v1 module {filepath.Join("testdata", "simple", "buf.gen.yaml"), filepath.Join("testdata", "simple")}, // v1 buf.gen.yaml, v2 module {filepath.Join("testdata", "simple", "buf.gen.yaml"), filepath.Join("testdata", "v2", "simple")}, // v2 buf.gen.yaml, v1 module {filepath.Join("testdata", "v2", "simple", "buf.gen.yaml"), filepath.Join("testdata", "simple")}, // v2 buf.gen.yaml, v2 module {filepath.Join("testdata", "v2", "simple", "buf.gen.yaml"), filepath.Join("testdata", "v2", "simple")}, } { tempDirPath := t.TempDir() testRunSuccess( t, "--output", tempDirPath, "--template", paths.template, paths.dir, ) _, err := os.Stat(filepath.Join(tempDirPath, "java", "a", "v1", "A.java")) require.NoError(t, err) } } func TestProtoFileRefIncludePackageFiles(t *testing.T) { t.Parallel() tempDirPath := t.TempDir() testRunSuccess( t, "--output", tempDirPath, "--template", filepath.Join("testdata", "protofileref", "buf.gen.yaml"), fmt.Sprintf("%s#include_package_files=true", filepath.Join("testdata", "protofileref", "a", "v1", "a.proto")), ) _, err := os.Stat(filepath.Join(tempDirPath, "java", "a", "v1", "A.java")) require.NoError(t, err) _, err = os.Stat(filepath.Join(tempDirPath, "java", "a", "v1", "B.java")) require.NoError(t, err) } func TestGenerateDuplicatePlugins(t *testing.T) { t.Parallel() tempDirPath := t.TempDir() testRunSuccess( t, "--output", tempDirPath, "--template", filepath.Join("testdata", "duplicate_plugins", "buf.gen.yaml"), filepath.Join("testdata", "duplicate_plugins"), ) _, err := os.Stat(filepath.Join(tempDirPath, "foo", "a", "v1", "A.java")) require.NoError(t, err) _, err = os.Stat(filepath.Join(tempDirPath, "bar", "a", "v1", "A.java")) require.NoError(t, err) } func TestGenerateDuplicatePluginsV2(t *testing.T) { t.Parallel() tempDirPath := t.TempDir() testRunSuccess( t, "--output", tempDirPath, "--template", filepath.Join("testdata", "v2", "duplicate_plugins", "buf.gen.yaml"), filepath.Join("testdata", "v2", "duplicate_plugins"), ) _, err := os.Stat(filepath.Join(tempDirPath, "foo", "a", "v1", "A.java")) require.NoError(t, err) _, err = os.Stat(filepath.Join(tempDirPath, "bar", "a", "v1", "A.java")) require.NoError(t, err) } func TestOutputWithPathEqualToExclude(t *testing.T) { t.Parallel() tempDirPath := t.TempDir() testRunStdoutStderr( t, nil, 1, ``, filepath.FromSlash(`Failure: cannot set the same path for both --path and --exclude-path: "testdata/paths/a/v1/a.proto"`), "--output", tempDirPath, "--template", filepath.Join("testdata", "paths", "buf.gen.yaml"), "--exclude-path", filepath.Join("testdata", "paths", "a", "v1", "a.proto"), "--path", filepath.Join("testdata", "paths", "a", "v1", "a.proto"), filepath.Join("testdata", "paths"), ) } func TestGenerateInsertionPoint(t *testing.T) { t.Parallel() testGenerateInsertionPointV1(t, ".", ".", filepath.Join("testdata", "insertion_point")) testGenerateInsertionPointV1(t, "gen/proto/insertion", "gen/proto/insertion", filepath.Join("testdata", "nested_insertion_point")) testGenerateInsertionPointV1(t, "gen/proto/insertion/", "./gen/proto/insertion", filepath.Join("testdata", "nested_insertion_point")) testGenerateInsertionPointV2(t, ".", ".", filepath.Join("testdata", "insertion_point")) testGenerateInsertionPointV2(t, "gen/proto/insertion", "gen/proto/insertion", filepath.Join("testdata", "nested_insertion_point")) testGenerateInsertionPointV2(t, "gen/proto/insertion/", "./gen/proto/insertion", filepath.Join("testdata", "nested_insertion_point")) } func TestGenerateInsertionPointFail(t *testing.T) { t.Parallel() successTemplate := ` version: v1 plugins: - name: insertion-point-receiver out: gen/proto/insertion - name: insertion-point-writer out: . ` testRunStdoutStderr( t, nil, 1, ``, `Failure: plugin insertion-point-writer: read test.txt: file does not exist`, filepath.Join("testdata", "simple"), // The input directory is irrelevant for these insertion points. "--template", successTemplate, "-o", t.TempDir(), ) } func TestGenerateInsertionPointFailV2(t *testing.T) { t.Parallel() successTemplate := ` version: v2 plugins: - protoc_builtin: insertion-point-receiver out: gen/proto/insertion - protoc_builtin: insertion-point-writer out: . managed: enabled: false ` testRunStdoutStderr( t, nil, 1, ``, `Failure: plugin insertion-point-writer: read test.txt: file does not exist`, filepath.Join("testdata", "v2", "simple"), // The input directory is irrelevant for these insertion points. "--template", successTemplate, "-o", t.TempDir(), ) } func TestGenerateDuplicateFileFail(t *testing.T) { t.Parallel() successTemplate := ` version: v1 plugins: - name: insertion-point-receiver out: . - name: insertion-point-receiver out: . ` testRunStdoutStderr( t, nil, 1, ``, `Failure: file "test.txt" was generated multiple times: once by plugin "insertion-point-receiver" and again by plugin "insertion-point-receiver"`, filepath.Join("testdata", "simple"), // The input directory is irrelevant for these insertion points. "--template", successTemplate, "-o", t.TempDir(), ) } func TestGenerateDuplicateFileFailV2(t *testing.T) { t.Parallel() successTemplate := ` version: v2 plugins: - protoc_builtin: insertion-point-receiver out: . - protoc_builtin: insertion-point-receiver out: . managed: enabled: false ` testRunStdoutStderr( t, nil, 1, ``, `Failure: file "test.txt" was generated multiple times: once by plugin "insertion-point-receiver" and again by plugin "insertion-point-receiver"`, filepath.Join("testdata", "v2", "simple"), // The input directory is irrelevant for these insertion points. "--template", successTemplate, "-o", t.TempDir(), ) } func TestGenerateInsertionPointMixedPathsFail(t *testing.T) { t.Parallel() wd, err := os.Getwd() require.NoError(t, err) testGenerateInsertionPointMixedPathsFailV1(t, ".", wd) testGenerateInsertionPointMixedPathsFailV1(t, wd, ".") testGenerateInsertionPointMixedPathsFailV2(t, ".", wd) testGenerateInsertionPointMixedPathsFailV2(t, wd, ".") } func TestGenerateDeleteOutDir(t *testing.T) { t.Parallel() testGenerateDeleteOuts(t, "", "foo") testGenerateDeleteOuts(t, "base", "foo") testGenerateDeleteOuts(t, "", "foo", "bar") testGenerateDeleteOuts(t, "", "foo", "bar", "foo") testGenerateDeleteOuts(t, "base", "foo", "bar") testGenerateDeleteOuts(t, "base", "foo", "bar", "foo") testGenerateDeleteOuts(t, "", "foo.jar") testGenerateDeleteOuts(t, "", "foo.zip") testGenerateDeleteOuts(t, "", "foo/bar.jar") testGenerateDeleteOuts(t, "", "foo/bar.zip") testGenerateDeleteOuts(t, "base", "foo.jar") testGenerateDeleteOuts(t, "base", "foo.zip") testGenerateDeleteOuts(t, "base", "foo/bar.jar") testGenerateDeleteOuts(t, "base", "foo/bar.zip") } func TestBoolPointerFlagTrue(t *testing.T) { t.Parallel() expected := true testParseBoolPointer(t, "test-name", &expected, "--test-name") } func TestBoolPointerFlagTrueSpecified(t *testing.T) { t.Parallel() expected := true testParseBoolPointer(t, "test-name", &expected, "--test-name=true") } func TestBoolPointerFlagFalseSpecified(t *testing.T) { t.Parallel() expected := false testParseBoolPointer(t, "test-name", &expected, "--test-name=false") } func TestBoolPointerFlagUnspecified(t *testing.T) { t.Parallel() testParseBoolPointer(t, "test-name", nil) } func testGenerateInsertionPointV1( t *testing.T, receiverOut string, writerOut string, expectedOutputPath string, ) { testGenerateInsertionPoint( t, receiverOut, writerOut, expectedOutputPath, ` version: v1 plugins: - name: insertion-point-receiver out: %s - name: insertion-point-writer out: %s `, ) } func testGenerateInsertionPointV2( t *testing.T, receiverOut string, writerOut string, expectedOutputPath string, ) { testGenerateInsertionPoint( t, receiverOut, writerOut, expectedOutputPath, ` version: v2 plugins: - protoc_builtin: insertion-point-receiver out: %s - protoc_builtin: insertion-point-writer out: %s `, ) } func testGenerateInsertionPoint( t *testing.T, receiverOut string, writerOut string, expectedOutputPath string, successTemplate string, ) { storageosProvider := storageos.NewProvider() tempDir, readWriteBucket := internaltesting.CopyReadBucketToTempDir( t.Context(), t, storageosProvider, storagemem.NewReadWriteBucket(), ) testRunSuccess( t, filepath.Join("testdata", "simple"), // The input directory is irrelevant for these insertion points. "--template", fmt.Sprintf(successTemplate, receiverOut, writerOut), "-o", tempDir, ) expectedOutput, err := storageosProvider.NewReadWriteBucket(expectedOutputPath) require.NoError(t, err) diff, err := storage.DiffBytes(t.Context(), expectedOutput, readWriteBucket) require.NoError(t, err) require.Empty(t, string(diff)) } func testGenerateInsertionPointMixedPathsFailV1( t *testing.T, receiverOut string, writerOut string, ) { testGenerateInsertionPointMixedPathsFail( t, receiverOut, writerOut, ` version: v1 plugins: - name: insertion-point-receiver out: %s - name: insertion-point-writer out: %s `, ) } func testGenerateInsertionPointMixedPathsFailV2( t *testing.T, receiverOut string, writerOut string, ) { testGenerateInsertionPointMixedPathsFail( t, receiverOut, writerOut, ` version: v2 plugins: - protoc_builtin: insertion-point-receiver out: %s - protoc_builtin: insertion-point-writer out: %s `, ) } // testGenerateInsertionPointMixedPathsFail demonstrates that insertion points are only // able to generate to the same output directory, even if the absolute path points to the // same place. This is equivalent to protoc's behavior. func testGenerateInsertionPointMixedPathsFail( t *testing.T, receiverOut string, writerOut string, successTemplate string, ) { testRunStdoutStderr( t, nil, 1, ``, `Failure: plugin insertion-point-writer: read test.txt: file does not exist`, filepath.Join("testdata", "simple"), // The input directory is irrelevant for these insertion points. "--template", fmt.Sprintf(successTemplate, receiverOut, writerOut), "-o", t.TempDir(), ) } func testCompareGeneratedStubs( t *testing.T, dirPath string, testPluginInfos []*testPluginInfo, ) { filePaths := buftesting.GetProtocFilePaths(t, dirPath, 100) actualProtocDir := t.TempDir() bufGenDir := t.TempDir() var actualProtocPluginFlags []string for _, testPluginInfo := range testPluginInfos { actualProtocPluginFlags = append(actualProtocPluginFlags, fmt.Sprintf("--%s_out=%s", testPluginInfo.name, actualProtocDir)) if testPluginInfo.opt != "" { actualProtocPluginFlags = append(actualProtocPluginFlags, fmt.Sprintf("--%s_opt=%s", testPluginInfo.name, testPluginInfo.opt)) } } buftesting.RunActualProtoc( t, false, false, dirPath, filePaths, map[string]string{ "PATH": os.Getenv("PATH"), }, nil, actualProtocPluginFlags..., ) genFlags := []string{ dirPath, "--template", newExternalConfigV1String(t, testPluginInfos, bufGenDir), } for _, filePath := range filePaths { genFlags = append( genFlags, "--path", filePath, ) } appcmdtesting.Run( t, func(name string) *appcmd.Command { return NewCommand( name, appext.NewBuilder(name), ) }, appcmdtesting.WithEnv(internaltesting.NewEnvFunc(t)), appcmdtesting.WithArgs(genFlags...), ) storageosProvider := storageos.NewProvider(storageos.ProviderWithSymlinks()) actualReadWriteBucket, err := storageosProvider.NewReadWriteBucket( actualProtocDir, storageos.ReadWriteBucketWithSymlinksIfSupported(), ) require.NoError(t, err) bufReadWriteBucket, err := storageosProvider.NewReadWriteBucket( bufGenDir, storageos.ReadWriteBucketWithSymlinksIfSupported(), ) require.NoError(t, err) diff, err := storage.DiffBytes( t.Context(), actualReadWriteBucket, bufReadWriteBucket, transformGolangProtocVersionToUnknown(t), ) require.NoError(t, err) assert.Empty(t, string(diff)) } func testCompareGeneratedStubsArchive( t *testing.T, dirPath string, testPluginInfos []*testPluginInfo, useJar bool, ) { fileExt := ".zip" if useJar { fileExt = ".jar" } filePaths := buftesting.GetProtocFilePaths(t, dirPath, 100) tempDir := t.TempDir() actualProtocFile := filepath.Join(tempDir, "actual-protoc"+fileExt) bufGenFile := filepath.Join(tempDir, "buf-generate"+fileExt) var actualProtocPluginFlags []string for _, testPluginInfo := range testPluginInfos { actualProtocPluginFlags = append(actualProtocPluginFlags, fmt.Sprintf("--%s_out=%s", testPluginInfo.name, actualProtocFile)) if testPluginInfo.opt != "" { actualProtocPluginFlags = append(actualProtocPluginFlags, fmt.Sprintf("--%s_opt=%s", testPluginInfo.name, testPluginInfo.opt)) } } buftesting.RunActualProtoc( t, false, false, dirPath, filePaths, map[string]string{ "PATH": os.Getenv("PATH"), }, nil, actualProtocPluginFlags..., ) genFlags := []string{ dirPath, "--template", newExternalConfigV1String(t, testPluginInfos, bufGenFile), } for _, filePath := range filePaths { genFlags = append( genFlags, "--path", filePath, ) } testRunSuccess( t, genFlags..., ) actualData, err := os.ReadFile(actualProtocFile) require.NoError(t, err) actualReadWriteBucket := storagemem.NewReadWriteBucket() err = storagearchive.Unzip( t.Context(), bytes.NewReader(actualData), int64(len(actualData)), actualReadWriteBucket, ) require.NoError(t, err) bufData, err := os.ReadFile(bufGenFile) require.NoError(t, err) bufReadWriteBucket := storagemem.NewReadWriteBucket() err = storagearchive.Unzip( t.Context(), bytes.NewReader(bufData), int64(len(bufData)), bufReadWriteBucket, ) require.NoError(t, err) diff, err := storage.DiffBytes( t.Context(), actualReadWriteBucket, bufReadWriteBucket, transformGolangProtocVersionToUnknown(t), ) require.NoError(t, err) assert.Empty(t, string(diff)) } func testRunSuccess(t *testing.T, args ...string) { appcmdtesting.Run( t, func(name string) *appcmd.Command { return NewCommand( name, appext.NewBuilder(name), ) }, appcmdtesting.WithEnv(internaltesting.NewEnvFunc(t)), appcmdtesting.WithArgs(args...), ) } func testGenerateDeleteOuts( t *testing.T, baseOutDirPath string, outputPaths ...string, ) { testGenerateDeleteOutsWithArgAndConfig(t, baseOutDirPath, nil, true, true, outputPaths) testGenerateDeleteOutsWithArgAndConfig(t, baseOutDirPath, nil, false, false, outputPaths) testGenerateDeleteOutsWithArgAndConfig(t, baseOutDirPath, []string{"--clean"}, false, true, outputPaths) testGenerateDeleteOutsWithArgAndConfig(t, baseOutDirPath, []string{"--clean"}, true, true, outputPaths) testGenerateDeleteOutsWithArgAndConfig(t, baseOutDirPath, []string{"--clean=true"}, true, true, outputPaths) testGenerateDeleteOutsWithArgAndConfig(t, baseOutDirPath, []string{"--clean=true"}, false, true, outputPaths) testGenerateDeleteOutsWithArgAndConfig(t, baseOutDirPath, []string{"--clean=false"}, true, false, outputPaths) testGenerateDeleteOutsWithArgAndConfig(t, baseOutDirPath, []string{"--clean=false"}, false, false, outputPaths) } func testGenerateDeleteOutsWithArgAndConfig( t *testing.T, baseOutDirPath string, cleanArgs []string, cleanOptionInConfig bool, expectedClean bool, outputPaths []string, ) { // Just add more builtins to the plugins slice below if this goes off require.True(t, len(outputPaths) < 4, "we want to have unique plugins to work with and this test is only set up for three plugins max right now") fullOutputPaths := outputPaths if baseOutDirPath != "" && baseOutDirPath != "." { fullOutputPaths = xslices.Map( outputPaths, func(outputPath string) string { return normalpath.Join(baseOutDirPath, outputPath) }, ) } ctx := t.Context() tmpDirPath := t.TempDir() storageBucket, err := storageos.NewProvider().NewReadWriteBucket(tmpDirPath) require.NoError(t, err) for _, fullOutputPath := range fullOutputPaths { switch normalpath.Ext(fullOutputPath) { case ".jar", ".zip": // Write a one-byte file to the location. We'll compare the size below as a simple test. require.NoError( t, storage.PutPath( ctx, storageBucket, fullOutputPath, []byte(`1`), ), ) default: // Write a file that won't be generated to the location. require.NoError( t, storage.PutPath( ctx, storageBucket, normalpath.Join(fullOutputPath, "foo.txt"), []byte(`1`), ), ) } } var templateBuilder strings.Builder _, _ = templateBuilder.WriteString(`version: v2 plugins: `) plugins := []string{"java", "cpp", "ruby"} for i, outputPath := range outputPaths { _, _ = templateBuilder.WriteString(` - protoc_builtin: `) _, _ = templateBuilder.WriteString(plugins[i]) _, _ = templateBuilder.WriteString("\n") _, _ = templateBuilder.WriteString(` out: `) _, _ = templateBuilder.WriteString(outputPath) _, _ = templateBuilder.WriteString("\n") } if cleanOptionInConfig { templateBuilder.WriteString("clean: true\n") } testRunStdoutStderr( t, nil, 0, ``, ``, append( []string{ filepath.Join("testdata", "simple"), "--template", templateBuilder.String(), "-o", filepath.Join(tmpDirPath, normalpath.Unnormalize(baseOutDirPath)), }, cleanArgs..., )..., ) for _, fullOutputPath := range fullOutputPaths { switch normalpath.Ext(fullOutputPath) { case ".jar", ".zip": data, err := storage.ReadPath( ctx, storageBucket, fullOutputPath, ) require.NoError(t, err) // Always expect non-fake data, because the existing ".jar" or ".zip" // file is always replaced by the output. This is the existing and correct // behavior. require.True(t, len(data) > 1, "expected non-fake data at %q", fullOutputPath) default: data, err := storage.ReadPath( ctx, storageBucket, normalpath.Join(fullOutputPath, "foo.txt"), ) if expectedClean { require.ErrorIs(t, err, fs.ErrNotExist) } else { require.NoError(t, err, "expected foo.txt at %q", fullOutputPath) require.NotNil(t, data, "expected foo.txt at %q", fullOutputPath) } } } } func testRunStdoutStderr(t *testing.T, stdin io.Reader, expectedExitCode int, expectedStdout string, expectedStderr string, args ...string) { appcmdtesting.Run( t, func(name string) *appcmd.Command { return NewCommand( name, appext.NewBuilder( name, appext.BuilderWithInterceptor( // TODO FUTURE: use the real interceptor. Currently in buf.go, NewBuilder receives appflag.BuilderWithInterceptor(newErrorInterceptor()). // However we cannot depend on newErrorInterceptor because it would create an import cycle, not to mention it needs to be exported first. // This can depend on newErrorInterceptor when it's moved to a separate package and made public. func(next func(context.Context, appext.Container) error) func(context.Context, appext.Container) error { return func(ctx context.Context, container appext.Container) error { err := next(ctx, container) if err == nil { return nil } return fmt.Errorf("Failure: %w", err) } }, ), ), ) }, appcmdtesting.WithExpectedExitCode(expectedExitCode), appcmdtesting.WithExpectedStdout(expectedStdout), appcmdtesting.WithExpectedStderr(expectedStderr), appcmdtesting.WithEnv(internaltesting.NewEnvFunc(t)), appcmdtesting.WithStdin(stdin), appcmdtesting.WithArgs(args...), ) } func testParseBoolPointer(t *testing.T, flagName string, expectedResult *bool, args ...string) { var boolPointer *bool flagSet := pflag.NewFlagSet("test flag set", pflag.ContinueOnError) bindBoolPointer(flagSet, flagName, &boolPointer, "test usage") err := flagSet.Parse(args) require.NoError(t, err) require.Equal(t, expectedResult, boolPointer) } func newExternalConfigV1String(t *testing.T, plugins []*testPluginInfo, out string) string { externalConfig := make(map[string]any) externalConfig["version"] = "v1" pluginConfigs := []map[string]string{} for _, plugin := range plugins { pluginConfigs = append( pluginConfigs, map[string]string{ "name": plugin.name, "opt": plugin.opt, "out": out, }, ) } externalConfig["plugins"] = pluginConfigs data, err := json.Marshal(externalConfig) require.NoError(t, err) return string(data) } type testPluginInfo struct { name string opt string } func transformGolangProtocVersionToUnknown(t *testing.T) storage.DiffOption { return storage.DiffWithTransform(func(_, _ string, content []byte) []byte { lines := bytes.Split(content, []byte("\n")) filteredLines := make([][]byte, 0, len(lines)) commentPrefix := []byte("//") protocVersionIndicator := []byte("protoc") for _, line := range lines { if !(bytes.HasPrefix(line, commentPrefix) && bytes.Contains(line, protocVersionIndicator)) { filteredLines = append(filteredLines, line) } } return bytes.Join(filteredLines, []byte("\n")) }) } ================================================ FILE: cmd/buf/internal/command/generate/generate_unix_test.go ================================================ // Copyright 2020-2026 Buf Technologies, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. //go:build aix || darwin || dragonfly || freebsd || (js && wasm) || linux || netbsd || openbsd || solaris package generate import ( "os" "path/filepath" "testing" "github.com/stretchr/testify/require" ) func TestProtoFileRef(t *testing.T) { t.Parallel() tempDirPath := t.TempDir() testRunSuccess( t, "--output", tempDirPath, "--template", filepath.Join("testdata", "protofileref", "buf.gen.yaml"), filepath.Join("testdata", "protofileref", "a", "v1", "a.proto"), ) _, err := os.Stat(filepath.Join(tempDirPath, "java", "a", "v1", "A.java")) require.NoError(t, err) _, err = os.Stat(filepath.Join(tempDirPath, "java", "a", "v1", "B.java")) require.Contains(t, err.Error(), "no such file or directory") } func TestOutputWithExclude(t *testing.T) { t.Parallel() tempDirPath := t.TempDir() testRunSuccess( t, filepath.Join("testdata", "paths"), "--output", tempDirPath, "--template", filepath.Join("testdata", "paths", "buf.gen.yaml"), "--exclude-path", filepath.Join("testdata", "paths", "a", "v1"), "--exclude-path", filepath.Join("testdata", "paths", "a", "v3"), ) _, err := os.Stat(filepath.Join(tempDirPath, "java", "a", "v2", "A.java")) require.NoError(t, err) _, err = os.Stat(filepath.Join(tempDirPath, "java", "b", "v1", "B.java")) require.NoError(t, err) _, err = os.Stat(filepath.Join(tempDirPath, "java", "a", "v1", "A.java")) require.Error(t, err) require.Contains(t, err.Error(), "no such file or directory") _, err = os.Stat(filepath.Join(tempDirPath, "java", "a", "v3", "A.java")) require.Error(t, err) require.Contains(t, err.Error(), "no such file or directory") _, err = os.Stat(filepath.Join(tempDirPath, "java", "a", "v3", "foo", "Foo.java")) require.Error(t, err) require.Contains(t, err.Error(), "no such file or directory") _, err = os.Stat(filepath.Join(tempDirPath, "java", "a", "v3", "bar", "Bar.java")) require.Error(t, err) require.Contains(t, err.Error(), "no such file or directory") } func TestOutputWithPathWithinExclude(t *testing.T) { t.Parallel() tempDirPath := t.TempDir() // This is new post-refactor. Before, we gave precedence to --path. While a change, // doing --path foo/bar --exclude-path foo seems like a bug rather than expected behavior to maintain. testRunStdoutStderr( t, nil, 1, "", filepath.FromSlash(`Failure: excluded path "testdata/paths/a" contains targeted path "testdata/paths/a/v1/a.proto", which means all paths in "testdata/paths/a/v1/a.proto" will be excluded`), "--output", tempDirPath, "--template", filepath.Join("testdata", "paths", "buf.gen.yaml"), "--path", filepath.Join("testdata", "paths", "a", "v1", "a.proto"), "--exclude-path", filepath.Join("testdata", "paths", "a"), ) } func TestOutputWithExcludeWithinPath(t *testing.T) { t.Parallel() tempDirPath := t.TempDir() testRunSuccess( t, "--output", tempDirPath, "--template", filepath.Join("testdata", "paths", "buf.gen.yaml"), "--exclude-path", filepath.Join("testdata", "paths", "a", "v1", "a.proto"), "--path", filepath.Join("testdata", "paths", "a"), filepath.Join("testdata", "paths"), ) _, err := os.Stat(filepath.Join(tempDirPath, "java", "a", "v2", "A.java")) require.NoError(t, err) _, err = os.Stat(filepath.Join(tempDirPath, "java", "b", "v1", "B.java")) require.Error(t, err) require.Contains(t, err.Error(), "no such file or directory") _, err = os.Stat(filepath.Join(tempDirPath, "java", "a", "v1", "A.java")) require.Error(t, err) require.Contains(t, err.Error(), "no such file or directory") } func TestOutputWithNestedExcludeAndTargetPaths(t *testing.T) { t.Parallel() tempDirPath := t.TempDir() // This is new post-refactor. Before, we gave precedence to --path. While a change, // doing --path foo/bar --exclude-path foo seems like a bug rather than expected behavior to maintain. testRunStdoutStderr( t, nil, 1, "", filepath.FromSlash(`Failure: excluded path "testdata/paths/a/v3" contains targeted path "testdata/paths/a/v3/foo", which means all paths in "testdata/paths/a/v3/foo" will be excluded`), "--output", tempDirPath, "--template", filepath.Join("testdata", "paths", "buf.gen.yaml"), "--exclude-path", filepath.Join("testdata", "paths", "a", "v3", "foo", "bar.proto"), "--exclude-path", filepath.Join("testdata", "paths", "a", "v3"), "--path", filepath.Join("testdata", "paths", "a", "v3", "foo"), filepath.Join("testdata", "paths"), ) } func TestWorkspaceGenerateWithExcludeAndTargetPaths(t *testing.T) { t.Parallel() tempDirPath := t.TempDir() // This is new post-refactor. Before, we gave precedence to --path. While a change, // doing --path foo/bar --exclude-path foo seems like a bug rather than expected behavior to maintain. testRunStdoutStderr( t, nil, 1, "", filepath.FromSlash(`Failure: excluded path "a/v3" contains targeted path "a/v3/foo", which means all paths in "a/v3/foo" will be excluded`), "--output", tempDirPath, "--template", filepath.Join("testdata", "workspace", "buf.gen.yaml"), "--exclude-path", filepath.Join("testdata", "workspace", "a", "v3", "foo", "bar.proto"), "--exclude-path", filepath.Join("testdata", "workspace", "a", "v3"), "--path", filepath.Join("testdata", "workspace", "a", "v3", "foo"), "--exclude-path", filepath.Join("testdata", "workspace", "b", "v1", "foo.proto"), filepath.Join("testdata", "workspace"), ) } ================================================ FILE: cmd/buf/internal/command/generate/generate_windows_test.go ================================================ // Copyright 2020-2026 Buf Technologies, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. //go:build windows package generate import ( "os" "path/filepath" "testing" "github.com/stretchr/testify/require" ) func TestProtoFileRef(t *testing.T) { tempDirPath := t.TempDir() testRunSuccess( t, "--output", tempDirPath, "--template", filepath.Join("testdata", "protofileref", "buf.gen.yaml"), filepath.Join("testdata", "protofileref", "a", "v1", "a.proto"), ) _, err := os.Stat(filepath.Join(tempDirPath, "java", "a", "v1", "A.java")) require.NoError(t, err) _, err = os.Stat(filepath.Join(tempDirPath, "java", "a", "v1", "B.java")) require.Contains(t, err.Error(), "The system cannot find the file specified.") } func TestOutputWithExclude(t *testing.T) { tempDirPath := t.TempDir() testRunSuccess( t, "--output", tempDirPath, "--template", filepath.Join("testdata", "paths", "buf.gen.yaml"), "--exclude-path", filepath.Join("testdata", "paths", "a", "v1"), "--exclude-path", filepath.Join("testdata", "paths", "a", "v3"), filepath.Join("testdata", "paths"), ) _, err := os.Stat(filepath.Join(tempDirPath, "java", "a", "v2", "A.java")) require.NoError(t, err) _, err = os.Stat(filepath.Join(tempDirPath, "java", "b", "v1", "B.java")) require.NoError(t, err) _, err = os.Stat(filepath.Join(tempDirPath, "java", "a", "v1", "A.java")) require.Error(t, err) require.Contains(t, err.Error(), "The system cannot find the path specified.") _, err = os.Stat(filepath.Join(tempDirPath, "java", "a", "v3", "A.java")) require.Error(t, err) require.Contains(t, err.Error(), "The system cannot find the path specified.") _, err = os.Stat(filepath.Join(tempDirPath, "java", "a", "v3", "foo", "Foo.java")) require.Error(t, err) require.Contains(t, err.Error(), "The system cannot find the path specified.") _, err = os.Stat(filepath.Join(tempDirPath, "java", "a", "v3", "bar", "Bar.java")) require.Error(t, err) require.Contains(t, err.Error(), "The system cannot find the path specified.") } func TestOutputWithPathWithinExclude(t *testing.T) { tempDirPath := t.TempDir() testRunStdoutStderr( t, nil, 1, ``, // This is new post-refactor. Before, we gave precedence to --path. While a change, // doing --path foo/bar --exclude-path foo seems like a bug rather than expected behavior to maintain. `Failure: excluded path "testdata\paths\a" contains targeted path "testdata\paths\a\v1\a.proto", which means all paths in "testdata\paths\a\v1\a.proto" will be excluded`, "--output", tempDirPath, "--template", filepath.Join("testdata", "paths", "buf.gen.yaml"), "--path", filepath.Join("testdata", "paths", "a", "v1", "a.proto"), "--exclude-path", filepath.Join("testdata", "paths", "a"), ) } func TestOutputWithExcludeWithinPath(t *testing.T) { tempDirPath := t.TempDir() testRunSuccess( t, "--output", tempDirPath, "--template", filepath.Join("testdata", "paths", "buf.gen.yaml"), "--exclude-path", filepath.Join("testdata", "paths", "a", "v1", "a.proto"), "--path", filepath.Join("testdata", "paths", "a"), filepath.Join("testdata", "paths"), ) _, err := os.Stat(filepath.Join(tempDirPath, "java", "a", "v2", "A.java")) require.NoError(t, err) _, err = os.Stat(filepath.Join(tempDirPath, "java", "b", "v1", "B.java")) require.Contains(t, err.Error(), "The system cannot find the path specified.") _, err = os.Stat(filepath.Join(tempDirPath, "java", "a", "v1", "A.java")) require.Contains(t, err.Error(), "The system cannot find the path specified.") } func TestOutputWithNestedExcludeAndTargetPaths(t *testing.T) { tempDirPath := t.TempDir() testRunStdoutStderr( t, nil, 1, ``, // This is new post-refactor. Before, we gave precedence to --path. While a change, // doing --path foo/bar --exclude-path foo seems like a bug rather than expected behavior to maintain. `Failure: excluded path "testdata\paths\a\v3" contains targeted path "testdata\paths\a\v3\foo", which means all paths in "testdata\paths\a\v3\foo" will be excluded`, "--output", tempDirPath, "--template", filepath.Join("testdata", "paths", "buf.gen.yaml"), "--exclude-path", filepath.Join("testdata", "paths", "a", "v3", "foo", "bar.proto"), "--exclude-path", filepath.Join("testdata", "paths", "a", "v3"), "--path", filepath.Join("testdata", "paths", "a", "v3", "foo"), filepath.Join("testdata", "paths"), ) } func TestWorkspaceGenerateWithExcludeAndTargetPaths(t *testing.T) { tempDirPath := t.TempDir() testRunStdoutStderr( t, nil, 1, ``, // This is new post-refactor. Before, we gave precedence to --path. While a change, // doing --path foo/bar --exclude-path foo seems like a bug rather than expected behavior to maintain. `Failure: excluded path "testdata\workspace\a\v3" contains targeted path "testdata\workspace\a\v3\foo", which means all paths in "testdata\workspace\a\v3\foo" will be excluded`, "--output", tempDirPath, "--template", filepath.Join("testdata", "workspace", "buf.gen.yaml"), "--exclude-path", filepath.Join("testdata", "workspace", "a", "v3", "foo", "bar.proto"), "--exclude-path", filepath.Join("testdata", "workspace", "a", "v3"), "--path", filepath.Join("testdata", "workspace", "a", "v3", "foo"), "--exclude-path", filepath.Join("testdata", "workspace", "b", "v1", "foo.proto"), ) } ================================================ FILE: cmd/buf/internal/command/generate/internal/protoc-gen-top-level-type-names-yaml/main.go ================================================ // Copyright 2020-2026 Buf Technologies, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package main import ( "context" "path/filepath" "sort" "strings" "github.com/bufbuild/protoplugin" "gopkg.in/yaml.v3" ) const fileExt = ".top-level-type-names.yaml" func main() { protoplugin.Main(protoplugin.HandlerFunc(handle)) } func handle( _ context.Context, _ protoplugin.PluginEnv, responseWriter protoplugin.ResponseWriter, request protoplugin.Request, ) error { fileDescriptors, err := request.FileDescriptorsToGenerate() if err != nil { return err } for _, fileDescriptor := range fileDescriptors { externalFile := &externalFile{} enumDescriptors := fileDescriptor.Enums() for i := range enumDescriptors.Len() { externalFile.Enums = append(externalFile.Enums, string(enumDescriptors.Get(i).FullName())) } messageDescriptors := fileDescriptor.Messages() for i := range messageDescriptors.Len() { externalFile.Messages = append(externalFile.Messages, string(messageDescriptors.Get(i).FullName())) } serviceDescriptors := fileDescriptor.Services() for i := range serviceDescriptors.Len() { externalFile.Services = append(externalFile.Services, string(serviceDescriptors.Get(i).FullName())) } sort.Strings(externalFile.Enums) sort.Strings(externalFile.Messages) sort.Strings(externalFile.Services) data, err := yaml.Marshal(externalFile) if err != nil { return err } responseWriter.AddFile( strings.TrimSuffix(fileDescriptor.Path(), filepath.Ext(filepath.FromSlash(fileDescriptor.Path())))+fileExt, string(data), ) } return nil } type externalFile struct { Enums []string `json:"enums,omitempty" yaml:"enums,omitempty"` Messages []string `json:"messages,omitempty" yaml:"messages,omitempty"` Services []string `json:"services,omitempty" yaml:"services,omitempty"` } ================================================ FILE: cmd/buf/internal/command/generate/testdata/duplicate_plugins/a.proto ================================================ syntax = "proto3"; package a.v1; message Foo {} ================================================ FILE: cmd/buf/internal/command/generate/testdata/duplicate_plugins/buf.gen.yaml ================================================ # The same plugin can be executed more than once, as long as they are written # to different output directories. version: v1 plugins: - name: java out: foo - name: java out: bar ================================================ FILE: cmd/buf/internal/command/generate/testdata/insertion/test.proto ================================================ syntax = "proto3"; package test; message Test { string name = 1; } ================================================ FILE: cmd/buf/internal/command/generate/testdata/insertion_point/test.txt ================================================ // The following line represents an insertion point named 'example'. // We include a few indentation to verify the whitespace is preserved // in the inserted content. // // Include this comment on the 'example' insertion point. // This is another example where whitespaces are preserved. // And this demonstrates a newline literal (\n). // And don't forget the windows newline literal (\r\n). // @@protoc_insertion_point(example) // // The 'other' insertion point is also included so that we verify // multiple insertion points can be written in a single invocation. // // Include this comment on the 'other' insertion point. // @@protoc_insertion_point(other) // // Note that all text should be added above the insertion points. ================================================ FILE: cmd/buf/internal/command/generate/testdata/nested_insertion_point/gen/proto/insertion/test.txt ================================================ // The following line represents an insertion point named 'example'. // We include a few indentation to verify the whitespace is preserved // in the inserted content. // // Include this comment on the 'example' insertion point. // This is another example where whitespaces are preserved. // And this demonstrates a newline literal (\n). // And don't forget the windows newline literal (\r\n). // @@protoc_insertion_point(example) // // The 'other' insertion point is also included so that we verify // multiple insertion points can be written in a single invocation. // // Include this comment on the 'other' insertion point. // @@protoc_insertion_point(other) // // Note that all text should be added above the insertion points. ================================================ FILE: cmd/buf/internal/command/generate/testdata/paths/a/v1/a.proto ================================================ syntax = "proto3"; package a.v1; message Foo {} ================================================ FILE: cmd/buf/internal/command/generate/testdata/paths/a/v2/a.proto ================================================ syntax = "proto3"; package a.v2; message Foo { string key = 1; } ================================================ FILE: cmd/buf/internal/command/generate/testdata/paths/a/v3/a.proto ================================================ syntax = "proto3"; package a.v3; message Foo { string key = 1; string value = 2; } ================================================ FILE: cmd/buf/internal/command/generate/testdata/paths/a/v3/foo/bar.proto ================================================ syntax = "proto3"; package a.v3.foo; message Bar { } ================================================ FILE: cmd/buf/internal/command/generate/testdata/paths/a/v3/foo/foo.proto ================================================ syntax = "proto3"; package a.v3.foo; message Foo { string id = 1; } ================================================ FILE: cmd/buf/internal/command/generate/testdata/paths/b/v1/b.proto ================================================ syntax = "proto3"; package b.v1; message Bar {} ================================================ FILE: cmd/buf/internal/command/generate/testdata/paths/buf.gen.yaml ================================================ version: v1 plugins: - name: java out: java ================================================ FILE: cmd/buf/internal/command/generate/testdata/paths/buf.yaml ================================================ version: v1 ================================================ FILE: cmd/buf/internal/command/generate/testdata/protofileref/a/v1/a.proto ================================================ syntax = "proto3"; package a.v1; message Foo {} ================================================ FILE: cmd/buf/internal/command/generate/testdata/protofileref/a/v1/b.proto ================================================ syntax = "proto3"; package a.v1; message Bar {} ================================================ FILE: cmd/buf/internal/command/generate/testdata/protofileref/buf.gen.yaml ================================================ version: v1 plugins: - name: java out: java ================================================ FILE: cmd/buf/internal/command/generate/testdata/protofileref/buf.yaml ================================================ version: v1 ================================================ FILE: cmd/buf/internal/command/generate/testdata/simple/a/v1/a.proto ================================================ syntax = "proto3"; package a.v1; message Foo {} ================================================ FILE: cmd/buf/internal/command/generate/testdata/simple/buf.gen.yaml ================================================ version: v1 plugins: - name: java out: java ================================================ FILE: cmd/buf/internal/command/generate/testdata/simple/buf.yaml ================================================ version: v1 ================================================ FILE: cmd/buf/internal/command/generate/testdata/types/a/v1/a.proto ================================================ syntax = "proto3"; package a.v1; message Foo {} ================================================ FILE: cmd/buf/internal/command/generate/testdata/types/buf.gen.yaml ================================================ version: v1 plugins: - name: java out: java types: - "a.v1.Foo" ================================================ FILE: cmd/buf/internal/command/generate/testdata/types/buf.yaml ================================================ version: v1 ================================================ FILE: cmd/buf/internal/command/generate/testdata/v2/duplicate_plugins/a.proto ================================================ syntax = "proto3"; package a.v1; message Foo {} ================================================ FILE: cmd/buf/internal/command/generate/testdata/v2/duplicate_plugins/buf.gen.yaml ================================================ # The same plugin can be executed more than once, as long as they are written # to different output directories. version: v2 plugins: - protoc_builtin: java out: foo - protoc_builtin: java out: bar managed: enabled: false ================================================ FILE: cmd/buf/internal/command/generate/testdata/v2/local_plugin/a/v1/a.proto ================================================ syntax = "proto3"; package a.v1; message Foo {} message Bar {} ================================================ FILE: cmd/buf/internal/command/generate/testdata/v2/local_plugin/b/v1/b.proto ================================================ syntax = "proto3"; package b.v1; message Foo {} message Bar {} ================================================ FILE: cmd/buf/internal/command/generate/testdata/v2/local_plugin/buf.basic.gen.yaml ================================================ version: v2 plugins: - local: protoc-gen-top-level-type-names-yaml out: gen ================================================ FILE: cmd/buf/internal/command/generate/testdata/v2/local_plugin/buf.exclude.paths.gen.yaml ================================================ version: v2 plugins: - local: protoc-gen-top-level-type-names-yaml out: gen strategy: all inputs: - directory: testdata/v2/local_plugin exclude_paths: - testdata/v2/local_plugin/a ================================================ FILE: cmd/buf/internal/command/generate/testdata/v2/local_plugin/buf.paths.gen.yaml ================================================ version: v2 plugins: - local: protoc-gen-top-level-type-names-yaml out: gen strategy: all inputs: - directory: testdata/v2/local_plugin paths: - testdata/v2/local_plugin/a ================================================ FILE: cmd/buf/internal/command/generate/testdata/v2/local_plugin/buf.types.gen.yaml ================================================ version: v2 plugins: - local: protoc-gen-top-level-type-names-yaml out: gen strategy: all inputs: - directory: ./testdata/v2/local_plugin types: - a.v1.Foo ================================================ FILE: cmd/buf/internal/command/generate/testdata/v2/local_plugin/buf.yaml ================================================ version: v2 ================================================ FILE: cmd/buf/internal/command/generate/testdata/v2/simple/a/v1/a.proto ================================================ syntax = "proto3"; package a.v1; message Foo {} ================================================ FILE: cmd/buf/internal/command/generate/testdata/v2/simple/buf.gen.yaml ================================================ version: v2 plugins: - protoc_builtin: java out: java managed: enabled: false ================================================ FILE: cmd/buf/internal/command/generate/testdata/v2/simple/buf.yaml ================================================ version: v2 modules: - path: . lint: except: - PACKAGE_NO_IMPORT_CYCLE ================================================ FILE: cmd/buf/internal/command/generate/testdata/v2/types/a/v1/a.proto ================================================ syntax = "proto3"; package a.v1; import "b/v1/b.proto"; import "pkg/v1/options.proto"; message Foo { option (pkg.v1.message_foo).foo = "str"; message Bar { option (pkg.v1.message_bar).bar = "str"; string bar = 1; } Bar nested_bar = 1; b.v1.Baz baz = 2; } message Empty {} message FooBar { Foo foo = 1; Foo.Bar bar = 2; } ================================================ FILE: cmd/buf/internal/command/generate/testdata/v2/types/b/v1/b.proto ================================================ syntax = "proto3"; package b.v1; import "pkg/v1/options.proto"; message Baz { option (pkg.v1.message_baz) = "str"; } ================================================ FILE: cmd/buf/internal/command/generate/testdata/v2/types/buf.gen.invalid.yaml ================================================ version: v2 plugins: - local: protoc-gen-top-level-type-names-yaml out: gen strategy: all inputs: - directory: ./testdata/v2/types types: - "a.v1.Foo.Bar" exclude_types: - "a.v1.Foo" ================================================ FILE: cmd/buf/internal/command/generate/testdata/v2/types/buf.gen.yaml ================================================ version: v2 managed: enabled: true override: - file_option: java_package_prefix value: net plugins: - local: protoc-gen-top-level-type-names-yaml out: gen strategy: all types: - "a.v1.Foo" exclude_types: - "a.v1.Foo.Bar" - "pkg.v1.message_foo" - "pkg.v1.message_bar" inputs: - directory: ./testdata/v2/types types: - "a.v1.FooBar" exclude_types: - "a.v1.Empty" - "pkg.v1.message_baz" ================================================ FILE: cmd/buf/internal/command/generate/testdata/v2/types/buf.yaml ================================================ version: v2 ================================================ FILE: cmd/buf/internal/command/generate/testdata/v2/types/pkg/v1/options.proto ================================================ syntax = "proto3"; package pkg.v1; import "google/protobuf/descriptor.proto"; message OptionFoo { string foo = 1; } message OptionBar { string bar = 1; } extend google.protobuf.MessageOptions { optional OptionFoo message_foo = 50000; optional OptionBar message_bar = 50001; optional string message_baz = 50002; } ================================================ FILE: cmd/buf/internal/command/generate/testdata/workspace/a/v1/a.proto ================================================ syntax = "proto3"; package a.v1; message Foo {} ================================================ FILE: cmd/buf/internal/command/generate/testdata/workspace/a/v2/a.proto ================================================ syntax = "proto3"; package a.v2; message Foo { string key = 1; } ================================================ FILE: cmd/buf/internal/command/generate/testdata/workspace/a/v3/a.proto ================================================ syntax = "proto3"; package a.v3; message Foo { string key = 1; string value = 2; } ================================================ FILE: cmd/buf/internal/command/generate/testdata/workspace/a/v3/foo/bar.proto ================================================ syntax = "proto3"; package a.v3.foo; message Bar { } ================================================ FILE: cmd/buf/internal/command/generate/testdata/workspace/a/v3/foo/foo.proto ================================================ syntax = "proto3"; package a.v3.foo; message Foo { } ================================================ FILE: cmd/buf/internal/command/generate/testdata/workspace/b/v1/b.proto ================================================ syntax = "proto3"; package b.v1; message Bar {} ================================================ FILE: cmd/buf/internal/command/generate/testdata/workspace/b/v1/foo.proto ================================================ syntax = "proto3"; package b.foo; message Foo { } ================================================ FILE: cmd/buf/internal/command/generate/testdata/workspace/buf.gen.yaml ================================================ version: v1 plugins: - name: java out: java ================================================ FILE: cmd/buf/internal/command/generate/testdata/workspace/buf.work.yaml ================================================ version: v1 directories: - a - b ================================================ FILE: cmd/buf/internal/command/lint/lint.go ================================================ // Copyright 2020-2026 Buf Technologies, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package lint import ( "context" "errors" "fmt" "buf.build/go/app/appcmd" "buf.build/go/app/appext" "buf.build/go/standard/xstrings" "github.com/bufbuild/buf/private/buf/bufcli" "github.com/bufbuild/buf/private/buf/bufctl" "github.com/bufbuild/buf/private/bufpkg/bufanalysis" "github.com/bufbuild/buf/private/bufpkg/bufcheck" "github.com/bufbuild/buf/private/bufpkg/bufconfig" "github.com/spf13/pflag" ) const ( errorFormatFlagName = "error-format" configFlagName = "config" pathsFlagName = "path" excludePathsFlagName = "exclude-path" disableSymlinksFlagName = "disable-symlinks" ) // NewCommand returns a new Command. func NewCommand( name string, builder appext.SubCommandBuilder, ) *appcmd.Command { flags := newFlags() return &appcmd.Command{ Use: name + " ", Short: "Run linting on Protobuf files", Long: bufcli.GetInputLong(`the source, module, or Image to lint`), Args: appcmd.MaximumNArgs(1), Run: builder.NewRunFunc( func(ctx context.Context, container appext.Container) error { return run(ctx, container, flags) }, ), BindFlags: flags.Bind, } } type flags struct { ErrorFormat string Config string Paths []string ExcludePaths []string DisableSymlinks bool // special InputHashtag string } func newFlags() *flags { return &flags{} } func (f *flags) Bind(flagSet *pflag.FlagSet) { bufcli.BindInputHashtag(flagSet, &f.InputHashtag) bufcli.BindPaths(flagSet, &f.Paths, pathsFlagName) bufcli.BindExcludePaths(flagSet, &f.ExcludePaths, excludePathsFlagName) bufcli.BindDisableSymlinks(flagSet, &f.DisableSymlinks, disableSymlinksFlagName) flagSet.StringVar( &f.ErrorFormat, errorFormatFlagName, "text", fmt.Sprintf( "The format for build errors or check violations printed to stdout. Must be one of %s", xstrings.SliceToString(bufcli.AllLintFormatStrings), ), ) flagSet.StringVar( &f.Config, configFlagName, "", `The buf.yaml file or data to use for configuration`, ) } func run( ctx context.Context, container appext.Container, flags *flags, ) (retErr error) { if err := bufcli.ValidateErrorFormatFlagLint(flags.ErrorFormat, errorFormatFlagName); err != nil { return err } // Parse out if this is config-ignore-yaml. // This is messed. controllerErrorFormat := flags.ErrorFormat if controllerErrorFormat == "config-ignore-yaml" { controllerErrorFormat = "text" } input, err := bufcli.GetInputValue(container, flags.InputHashtag, ".") if err != nil { return err } controller, err := bufcli.NewController( container, bufctl.WithDisableSymlinks(flags.DisableSymlinks), bufctl.WithFileAnnotationErrorFormat(controllerErrorFormat), bufctl.WithFileAnnotationsToStdout(), ) if err != nil { return err } wasmRuntime, err := bufcli.NewWasmRuntime(ctx, container) if err != nil { return err } defer func() { retErr = errors.Join(retErr, wasmRuntime.Close(ctx)) }() imageWithConfigs, checkClient, err := controller.GetTargetImageWithConfigsAndCheckClient( ctx, input, wasmRuntime, bufctl.WithTargetPaths(flags.Paths, flags.ExcludePaths), bufctl.WithConfigOverride(flags.Config), ) if err != nil { return err } var allFileAnnotations []bufanalysis.FileAnnotation // We add all check configs (both lint and breaking) as related configs to check if plugins // have rules configured. // We allocated twice the size of imageWithConfigs for both lint and breaking configs. allCheckConfigs := make([]bufconfig.CheckConfig, 0, len(imageWithConfigs)*2) for _, imageWithConfig := range imageWithConfigs { allCheckConfigs = append(allCheckConfigs, imageWithConfig.LintConfig()) allCheckConfigs = append(allCheckConfigs, imageWithConfig.BreakingConfig()) } for _, imageWithConfig := range imageWithConfigs { lintOptions := []bufcheck.LintOption{ bufcheck.WithPluginConfigs(imageWithConfig.PluginConfigs()...), bufcheck.WithPolicyConfigs(imageWithConfig.PolicyConfigs()...), bufcheck.WithRelatedCheckConfigs(allCheckConfigs...), } if err := checkClient.Lint( ctx, imageWithConfig.LintConfig(), imageWithConfig, lintOptions..., ); err != nil { var fileAnnotationSet bufanalysis.FileAnnotationSet if errors.As(err, &fileAnnotationSet) { allFileAnnotations = append(allFileAnnotations, fileAnnotationSet.FileAnnotations()...) } else { return err } } } if len(allFileAnnotations) > 0 { allFileAnnotationSet := bufanalysis.NewFileAnnotationSet(allFileAnnotations...) if flags.ErrorFormat == "config-ignore-yaml" { if err := bufcli.PrintFileAnnotationSetLintConfigIgnoreYAMLV1( container.Stdout(), allFileAnnotationSet, ); err != nil { return err } } else { if err := bufanalysis.PrintFileAnnotationSet( container.Stdout(), allFileAnnotationSet, flags.ErrorFormat, ); err != nil { return err } } return bufctl.ErrFileAnnotation } return nil } ================================================ FILE: cmd/buf/internal/command/lsfiles/lsfiles.go ================================================ // Copyright 2020-2026 Buf Technologies, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package lsfiles import ( "context" "encoding/json" "fmt" "sort" "strings" "buf.build/go/app/appcmd" "buf.build/go/app/appext" "buf.build/go/standard/xslices" "buf.build/go/standard/xstrings" "github.com/bufbuild/buf/private/buf/bufcli" "github.com/bufbuild/buf/private/buf/bufctl" "github.com/bufbuild/buf/private/bufpkg/bufanalysis" "github.com/bufbuild/buf/private/bufpkg/bufimage" "github.com/bufbuild/buf/private/gen/data/datawkt" "github.com/bufbuild/buf/private/pkg/uuidutil" "github.com/google/uuid" "github.com/spf13/pflag" ) const ( formatFlagName = "format" configFlagName = "config" errorFormatFlagName = "error-format" includeImportsFlagName = "include-imports" includeImportableFlagName = "include-importable" pathsFlagName = "path" excludePathsFlagName = "exclude-path" disableSymlinksFlagName = "disable-symlinks" asImportPathsFlagName = "as-import-paths" formatText = "text" formatJSON = "json" formatImport = "import" ) var ( allFormats = []string{formatText, formatJSON, formatImport} ) // NewCommand returns a new Command. func NewCommand( name string, builder appext.SubCommandBuilder, ) *appcmd.Command { flags := newFlags() return &appcmd.Command{ Use: name + " ", Short: "List Protobuf files", Long: bufcli.GetInputLong(`the source, module, or image to list from`), Args: appcmd.MaximumNArgs(1), Run: builder.NewRunFunc( func(ctx context.Context, container appext.Container) error { return run(ctx, container, flags) }, ), BindFlags: flags.Bind, } } type flags struct { Format string Config string IncludeImports bool IncludeImportable bool Paths []string ExcludePaths []string DisableSymlinks bool // Deprecated. This flag no longer has any effect as we don't build images anymore. ErrorFormat string // Deprecated AsImportPaths bool // special InputHashtag string } func newFlags() *flags { return &flags{} } func (f *flags) Bind(flagSet *pflag.FlagSet) { bufcli.BindInputHashtag(flagSet, &f.InputHashtag) bufcli.BindPaths(flagSet, &f.Paths, pathsFlagName) bufcli.BindExcludePaths(flagSet, &f.ExcludePaths, excludePathsFlagName) bufcli.BindDisableSymlinks(flagSet, &f.DisableSymlinks, disableSymlinksFlagName) flagSet.StringVar( &f.Config, configFlagName, "", `The buf.yaml file or data to use for configuration`, ) flagSet.StringVar( &f.Format, formatFlagName, formatText, fmt.Sprintf( `The format to print the .proto files. Must be one of %s`, xstrings.SliceToString(allFormats), ), ) flagSet.BoolVar( &f.IncludeImports, includeImportsFlagName, false, "Include imports", ) flagSet.BoolVar( &f.IncludeImportable, includeImportableFlagName, false, fmt.Sprintf( "Include all .proto files that are importable by the input. --%s is redundant if this is set", includeImportsFlagName, ), ) flagSet.StringVar( &f.ErrorFormat, errorFormatFlagName, "text", fmt.Sprintf( "The format for build errors printed to stderr. Must be one of %s", xstrings.SliceToString(bufanalysis.AllFormatStrings), ), ) _ = flagSet.MarkDeprecated(errorFormatFlagName, "this flag no longer has any effect") _ = flagSet.MarkHidden(errorFormatFlagName) flagSet.BoolVar( &f.AsImportPaths, asImportPathsFlagName, false, "Strip local directory paths and print filepaths as they are imported", ) _ = flagSet.MarkDeprecated(asImportPathsFlagName, fmt.Sprintf("use --%s=import instead", formatFlagName)) _ = flagSet.MarkHidden(asImportPathsFlagName) } func run( ctx context.Context, container appext.Container, flags *flags, ) error { if flags.AsImportPaths { flags.Format = formatImport } input, err := bufcli.GetInputValue(container, flags.InputHashtag, ".") if err != nil { return err } controller, err := bufcli.NewController( container, bufctl.WithDisableSymlinks(flags.DisableSymlinks), ) if err != nil { return err } // Sorted by Path. imageFileInfos, err := controller.GetImportableImageFileInfos( ctx, input, bufctl.WithTargetPaths(flags.Paths, flags.ExcludePaths), bufctl.WithConfigOverride(flags.Config), ) if err != nil { return err } if !flags.IncludeImportable { if !flags.IncludeImports { imageFileInfos = xslices.Filter( imageFileInfos, func(imageFileInfo bufimage.ImageFileInfo) bool { return !imageFileInfo.IsImport() }, ) } else { // Also automatically adds imported WKTs if not present. imageFileInfos, err = bufimage.ImageFileInfosWithOnlyTargetsAndTargetImports( ctx, datawkt.ReadBucket, imageFileInfos, ) if err != nil { return err } } } var formatFunc func(bufimage.ImageFileInfo) (string, error) switch flags.Format { case formatText: sort.Slice( imageFileInfos, func(i int, j int) bool { return imageFileInfos[i].ExternalPath() < imageFileInfos[j].ExternalPath() }, ) formatFunc = func(imageFileInfo bufimage.ImageFileInfo) (string, error) { return imageFileInfo.ExternalPath(), nil } case formatJSON: sort.Slice( imageFileInfos, func(i int, j int) bool { if imageFileInfos[i].LocalPath() < imageFileInfos[j].LocalPath() { return true } if imageFileInfos[i].LocalPath() > imageFileInfos[j].LocalPath() { return false } return imageFileInfos[i].Path() < imageFileInfos[j].Path() }, ) formatFunc = func(imageFileInfo bufimage.ImageFileInfo) (string, error) { data, err := json.Marshal(newExternalImageFileInfo(imageFileInfo)) if err != nil { return "", err } return string(data), nil } case formatImport: formatFunc = func(imageFileInfo bufimage.ImageFileInfo) (string, error) { return imageFileInfo.Path(), nil } default: return appcmd.NewInvalidArgumentErrorf("--%s must be one of %s", formatFlagName, strings.Join(allFormats, ", ")) } lines, err := xslices.MapError(imageFileInfos, formatFunc) if err != nil { return err } for _, line := range lines { if _, err := fmt.Fprintln(container.Stdout(), line); err != nil { return err } } return nil } type externalImageFileInfo struct { Path string `json:"path" yaml:"path"` ImportPath string `json:"import_path" yaml:"import_path"` Module string `json:"module" yaml:"module"` // Dashless Commit string `json:"commit" yaml:"commit"` IsImport bool `json:"is_import" yaml:"is_import"` } func newExternalImageFileInfo(imageFileInfo bufimage.ImageFileInfo) *externalImageFileInfo { var module string if moduleFullName := imageFileInfo.FullName(); moduleFullName != nil { module = moduleFullName.String() } var commit string if commitID := imageFileInfo.CommitID(); commitID != uuid.Nil { commit = uuidutil.ToDashless(commitID) } return &externalImageFileInfo{ Path: imageFileInfo.LocalPath(), // This seems backwards when you read it, but it is right: the Path is the import path, // the LocalPath is the path that a user would have for a file on disk. ImportPath: imageFileInfo.Path(), Module: module, Commit: commit, IsImport: imageFileInfo.IsImport(), } } ================================================ FILE: cmd/buf/internal/command/lsp/lspserve/lspserve.go ================================================ // Copyright 2020-2026 Buf Technologies, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Package lspserve defines the entry-point for the Buf LSP within the CLI. // // The actual implementation of the LSP lives under private/buf/buflsp package lspserve import ( "context" "errors" "fmt" "io" "net" "buf.build/go/app/appcmd" "buf.build/go/app/appext" "buf.build/go/standard/xio" "github.com/bufbuild/buf/private/buf/bufcli" "github.com/bufbuild/buf/private/buf/buflsp" "github.com/bufbuild/protocompile/experimental/incremental" "github.com/spf13/pflag" "go.lsp.dev/jsonrpc2" ) const ( // pipe is chosen because that's what the vscode LSP client expects. pipeFlagName = "pipe" ) // NewCommand constructs the CLI command for executing the LSP. func NewCommand( name string, builder appext.Builder, deprecated string, hidden bool, beta bool, ) *appcmd.Command { flags := newFlags() return &appcmd.Command{ Use: name, Short: "Start the language server", Args: appcmd.NoArgs, Deprecated: deprecated, Hidden: hidden, Run: builder.NewRunFunc( func(ctx context.Context, container appext.Container) error { if beta { bufcli.WarnBetaCommand(ctx, container) } return run(ctx, container, flags) }, ), BindFlags: flags.Bind, } } type flags struct { // A file path to a UNIX socket to use for IPC. If empty, stdio is used instead. PipePath string } // Bind sets up the CLI flags that the LSP needs. func (f *flags) Bind(flagSet *pflag.FlagSet) { flagSet.StringVar( &f.PipePath, pipeFlagName, "", "path to a UNIX socket to listen on; uses stdio if not specified", ) } func newFlags() *flags { return &flags{} } // run starts the LSP server and listens on the configured. func run( ctx context.Context, container appext.Container, flags *flags, ) (retErr error) { transport, err := dial(container, flags) if err != nil { return err } wktStore, err := bufcli.NewWKTStore(container) if err != nil { return err } wktBucket, err := wktStore.GetBucket(ctx) if err != nil { return err } controller, err := bufcli.NewController(container) if err != nil { return err } wasmRuntime, err := bufcli.NewWasmRuntime(ctx, container) if err != nil { return err } defer func() { retErr = errors.Join(retErr, wasmRuntime.Close(ctx)) }() conn, err := buflsp.Serve( ctx, bufcli.Version, wktBucket, container, controller, wasmRuntime, jsonrpc2.NewStream(transport), incremental.New(), ) if err != nil { return err } <-conn.Done() return conn.Err() } // dial opens a connection to the LSP client. func dial(container appext.Container, flags *flags) (io.ReadWriteCloser, error) { switch { case flags.PipePath != "": conn, err := net.Dial("unix", flags.PipePath) if err != nil { return nil, fmt.Errorf("could not open IPC socket %q: %w", flags.PipePath, err) } return conn, nil // TODO: Add other transport implementations, such as TCP, here! default: // Fall back to stdio by default. return xio.CompositeReadWriteCloser( container.Stdin(), container.Stdout(), xio.NopCloser, ), nil } } ================================================ FILE: cmd/buf/internal/command/mod/internal/internal.go ================================================ // Copyright 2020-2026 Buf Technologies, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package internal import ( "context" "errors" "fmt" "io/fs" "buf.build/go/app/appcmd" "buf.build/go/app/appext" "buf.build/go/bufplugin/check" "buf.build/go/standard/xslices" "buf.build/go/standard/xstrings" "github.com/bufbuild/buf/private/buf/bufcli" "github.com/bufbuild/buf/private/bufpkg/bufcheck" "github.com/bufbuild/buf/private/bufpkg/bufconfig" "github.com/bufbuild/buf/private/pkg/syserror" "github.com/spf13/pflag" ) const ( allFlagName = "all" configFlagName = "config" includeDeprecatedFlagName = "include-deprecated" formatFlagName = "format" versionFlagName = "version" ) // NewLSCommand returns a new ls Command. func NewLSCommand( name string, builder appext.SubCommandBuilder, ruleType check.RuleType, ) *appcmd.Command { flags := newFlags() return &appcmd.Command{ Use: name, Short: fmt.Sprintf("List %s rules", ruleType.String()), Args: appcmd.NoArgs, Deprecated: fmt.Sprintf(`use "buf config %s" instead. However, "buf mod %s" will continue to work.`, name, name), Hidden: true, Run: builder.NewRunFunc( func(ctx context.Context, container appext.Container) error { return lsRun( ctx, container, flags, name, ruleType, ) }, ), BindFlags: flags.Bind, } } type flags struct { All bool Config string IncludeDeprecated bool Format string Version string } func newFlags() *flags { return &flags{} } func (f *flags) Bind(flagSet *pflag.FlagSet) { flagSet.BoolVar( &f.All, allFlagName, false, "List all rules and not just those currently configured", ) flagSet.StringVar( &f.Config, configFlagName, "", fmt.Sprintf( `The buf.yaml file or data to use for configuration. Ignored if --%s or --%s is specified`, allFlagName, versionFlagName, ), ) flagSet.BoolVar( &f.IncludeDeprecated, includeDeprecatedFlagName, false, fmt.Sprintf( `Also print deprecated rules. Has no effect if --%s is not set.`, allFlagName, ), ) flagSet.StringVar( &f.Format, formatFlagName, "text", fmt.Sprintf( "The format to print rules as. Must be one of %s", xstrings.SliceToString(bufcli.AllRuleFormatStrings), ), ) flagSet.StringVar( &f.Version, versionFlagName, "", // do not set a default as we need to know if this is unset fmt.Sprintf( "List all the rules for the given configuration version. Implies --%s. Must be one of %s", allFlagName, xslices.Map( bufconfig.AllFileVersions, func(fileVersion bufconfig.FileVersion) string { return fileVersion.String() }, ), ), ) } func lsRun( ctx context.Context, container appext.Container, flags *flags, commandName string, ruleType check.RuleType, ) error { if flags.All { // We explicitly document that if all is set, config is ignored. // If a user wants to override the version while using all, they should use version. flags.Config = "" } if flags.Version != "" { // If version is set, all is implied, and we use the config override to specify the version. flags.All = true // This also results in config being ignored per the documentation. flags.Config = fmt.Sprintf(`{"version":"%s"}`, flags.Version) } bufYAMLFile, err := bufcli.GetBufYAMLFileForDirPathOrOverride(ctx, ".", flags.Config) if err != nil { if !errors.Is(err, fs.ErrNotExist) { return err } bufYAMLFile, err = bufconfig.NewBufYAMLFile( bufconfig.FileVersionV1, []bufconfig.ModuleConfig{ bufconfig.DefaultModuleConfigV1, }, nil, nil, nil, ) if err != nil { return err } } fileVersion := bufYAMLFile.FileVersion() if fileVersion == bufconfig.FileVersionV2 { return fmt.Errorf(`"buf mod %s" does not work for v2 buf.yaml files, use "buf config %s" instead`, commandName, commandName) } // BufYAMLFiles <=v1 never had plugins. client, err := bufcheck.NewClient( container.Logger(), bufcheck.ClientWithStderr(container.Stderr()), ) if err != nil { return err } var rules []bufcheck.Rule if flags.All { rules, err = client.AllRules(ctx, ruleType, bufYAMLFile.FileVersion()) if err != nil { return err } } else { moduleConfigs := bufYAMLFile.ModuleConfigs() if len(moduleConfigs) != 1 { return syserror.Newf("got %d ModuleConfigs for a v1beta1/v1 buf.yaml", len(moduleConfigs)) } var checkConfig bufconfig.CheckConfig switch ruleType { case check.RuleTypeLint: checkConfig = moduleConfigs[0].LintConfig() case check.RuleTypeBreaking: checkConfig = moduleConfigs[0].BreakingConfig() default: return fmt.Errorf("unknown check.RuleType: %v", ruleType) } rules, err = client.ConfiguredRules(ctx, ruleType, checkConfig) if err != nil { return err } } return bufcli.PrintRules( container.Stdout(), rules, flags.Format, flags.IncludeDeprecated, ) } ================================================ FILE: cmd/buf/internal/command/mod/modlsbreakingrules/modlsbreakingrules.go ================================================ // Copyright 2020-2026 Buf Technologies, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package modlsbreakingrules import ( "buf.build/go/app/appcmd" "buf.build/go/app/appext" "buf.build/go/bufplugin/check" "github.com/bufbuild/buf/cmd/buf/internal/command/mod/internal" ) // NewCommand returns a new Command. func NewCommand( name string, builder appext.SubCommandBuilder, ) *appcmd.Command { return internal.NewLSCommand( name, builder, check.RuleTypeBreaking, ) } ================================================ FILE: cmd/buf/internal/command/mod/modlslintrules/modlslintrules.go ================================================ // Copyright 2020-2026 Buf Technologies, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package modlslintrules import ( "buf.build/go/app/appcmd" "buf.build/go/app/appext" "buf.build/go/bufplugin/check" "github.com/bufbuild/buf/cmd/buf/internal/command/mod/internal" ) // NewCommand returns a new Command. func NewCommand( name string, builder appext.SubCommandBuilder, ) *appcmd.Command { return internal.NewLSCommand( name, builder, check.RuleTypeLint, ) } ================================================ FILE: cmd/buf/internal/command/mod/modopen/modopen.go ================================================ // Copyright 2020-2026 Buf Technologies, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package modopen import ( "context" "errors" "fmt" "io/fs" "buf.build/go/app/appcmd" "buf.build/go/app/appext" "github.com/bufbuild/buf/private/buf/bufcli" "github.com/bufbuild/buf/private/bufpkg/bufconfig" "github.com/bufbuild/buf/private/pkg/syserror" "github.com/cli/browser" ) const deprecationMessage = "this command is not supported for v2 buf.yaml files as v2 buf.yaml files contain multiple modules. However, this command will continue to work for v1 buf.yaml files." // NewCommand returns a new open Command. func NewCommand( name string, builder appext.SubCommandBuilder, ) *appcmd.Command { return &appcmd.Command{ Use: name + " ", Short: "Open the module's homepage in a web browser", Long: `The first argument is the directory with the buf.yaml of the module to open. The directory must have a buf.yaml that contains a single specified module name. The directory defaults to "." if no argument is specified.`, Deprecated: deprecationMessage, Hidden: true, Args: appcmd.MaximumNArgs(1), Run: builder.NewRunFunc( func(ctx context.Context, container appext.Container) error { return run(ctx, container) }, ), } } func run( ctx context.Context, container appext.Container, ) error { dirPath := "." if container.NumArgs() > 0 { dirPath = container.Arg(0) } bufYAMLFile, err := bufcli.GetBufYAMLFileForDirPath(ctx, dirPath) if err != nil { if errors.Is(err, fs.ErrNotExist) { return fmt.Errorf("no buf.yaml discovered in directory %s", dirPath) } return err } switch fileVersion := bufYAMLFile.FileVersion(); fileVersion { case bufconfig.FileVersionV1Beta1, bufconfig.FileVersionV1: moduleConfigs := bufYAMLFile.ModuleConfigs() if len(moduleConfigs) != 1 { return syserror.Newf("got %d ModuleConfigs for a v1beta1/v1 buf.yaml", len(moduleConfigs)) } moduleFullName := moduleConfigs[0].FullName() if moduleFullName == nil { return fmt.Errorf("%s/buf.yaml has no module name", dirPath) } return browser.OpenURL("https://" + moduleFullName.String()) case bufconfig.FileVersionV2: return errors.New(deprecationMessage) default: return syserror.Newf("unknown FileVersion: %v", fileVersion) } } ================================================ FILE: cmd/buf/internal/command/plugin/pluginprune/pluginprune.go ================================================ // Copyright 2020-2026 Buf Technologies, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package pluginprune import ( "context" "buf.build/go/app/appcmd" "buf.build/go/app/appext" "buf.build/go/standard/xslices" "github.com/bufbuild/buf/private/buf/bufcli" "github.com/bufbuild/buf/private/buf/bufworkspace" "github.com/bufbuild/buf/private/bufpkg/bufparse" "github.com/bufbuild/buf/private/bufpkg/bufplugin" ) // NewCommand returns a new Command. func NewCommand( name string, builder appext.SubCommandBuilder, ) *appcmd.Command { return &appcmd.Command{ Use: name + " ", Short: "Prune unused plugins from buf.lock", Long: `Plugins that are no longer configured in buf.yaml are removed from the buf.lock file. The first argument is the directory of your buf.yaml configuration file. Defaults to "." if no argument is specified.`, Args: appcmd.MaximumNArgs(1), Run: builder.NewRunFunc( func(ctx context.Context, container appext.Container) error { return run(ctx, container) }, ), } } func run( ctx context.Context, container appext.Container, ) error { dirPath := "." if container.NumArgs() > 0 { dirPath = container.Arg(0) } controller, err := bufcli.NewController(container) if err != nil { return err } workspaceDepManager, err := controller.GetWorkspaceDepManager(ctx, dirPath) if err != nil { return err } configuredRemotePluginRefs, err := workspaceDepManager.ConfiguredRemotePluginRefs(ctx) if err != nil { return err } return prune( ctx, xslices.Map( configuredRemotePluginRefs, func(pluginRef bufparse.Ref) string { return pluginRef.FullName().String() }, ), workspaceDepManager, ) } func prune( ctx context.Context, bufYAMLBasedRemotePluginNames []string, workspaceDepManager bufworkspace.WorkspaceDepManager, ) error { bufYAMLRemotePluginNames := xslices.ToStructMap(bufYAMLBasedRemotePluginNames) existingRemotePluginKeys, err := workspaceDepManager.ExistingBufLockFileRemotePluginKeys(ctx) if err != nil { return err } var prunedBufLockPluginKeys []bufplugin.PluginKey for _, existingRemotePluginKey := range existingRemotePluginKeys { // Check if an existing plugin key from the buf.lock is configured in the buf.yaml. if _, ok := bufYAMLRemotePluginNames[existingRemotePluginKey.FullName().String()]; ok { // If yes, then we keep it for the updated buf.lock. prunedBufLockPluginKeys = append(prunedBufLockPluginKeys, existingRemotePluginKey) } } // We keep the existing dep module keys as-is. existingDepModuleKeys, err := workspaceDepManager.ExistingBufLockFileDepModuleKeys(ctx) if err != nil { return err } existingRemotePolicyKeys, err := workspaceDepManager.ExistingBufLockFileRemotePolicyKeys(ctx) if err != nil { return err } existingPolicyNameToRemotePluginKeys, err := workspaceDepManager.ExistingBufLockFilePolicyNameToRemotePluginKeys(ctx) if err != nil { return err } return workspaceDepManager.UpdateBufLockFile(ctx, existingDepModuleKeys, prunedBufLockPluginKeys, existingRemotePolicyKeys, existingPolicyNameToRemotePluginKeys) } ================================================ FILE: cmd/buf/internal/command/plugin/pluginpush/pluginpush.go ================================================ // Copyright 2020-2026 Buf Technologies, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package pluginpush import ( "context" "fmt" "os" "slices" "strings" "buf.build/go/app/appcmd" "buf.build/go/app/appext" "buf.build/go/standard/xslices" "buf.build/go/standard/xstrings" "github.com/bufbuild/buf/private/buf/bufcli" "github.com/bufbuild/buf/private/bufpkg/bufparse" "github.com/bufbuild/buf/private/bufpkg/bufplugin" "github.com/bufbuild/buf/private/pkg/syserror" "github.com/spf13/pflag" ) const ( labelFlagName = "label" binaryFlagName = "binary" createFlagName = "create" createVisibilityFlagName = "create-visibility" createTypeFlagName = "create-type" sourceControlURLFlagName = "source-control-url" ) // NewCommand returns a new Command. func NewCommand( name string, builder appext.SubCommandBuilder, ) *appcmd.Command { flags := newFlags() return &appcmd.Command{ Use: name + " ", Short: "Push a check plugin to a registry", Long: `The first argument is the plugin full name in the format .`, Args: appcmd.MaximumNArgs(1), Run: builder.NewRunFunc( func(ctx context.Context, container appext.Container) error { return run(ctx, container, flags) }, ), BindFlags: flags.Bind, } } type flags struct { Labels []string Binary string Create bool CreateVisibility string CreateType string SourceControlURL string } func newFlags() *flags { return &flags{} } func (f *flags) Bind(flagSet *pflag.FlagSet) { bufcli.BindCreateVisibility(flagSet, &f.CreateVisibility, createVisibilityFlagName, createFlagName) flagSet.StringSliceVar( &f.Labels, labelFlagName, nil, "Associate the label with the plugins pushed. Can be used multiple times.", ) flagSet.StringVar( &f.Binary, binaryFlagName, "", "The path to the Wasm binary file to push.", ) flagSet.BoolVar( &f.Create, createFlagName, false, fmt.Sprintf( "Create the plugin if it does not exist. Defaults to creating a private plugin on the BSR if --%s is not set. Must be used with --%s.", createVisibilityFlagName, createTypeFlagName, ), ) flagSet.StringVar( &f.CreateType, createTypeFlagName, "", fmt.Sprintf( "The plugin's type setting, if created. Can only be set with --%s. Must be one of %s", createFlagName, xstrings.SliceToString(bufplugin.AllPluginTypeStrings), ), ) flagSet.StringVar( &f.SourceControlURL, sourceControlURLFlagName, "", "The URL for viewing the source code of the pushed plugins (e.g. the specific commit in source control).", ) } func run( ctx context.Context, container appext.Container, flags *flags, ) (retErr error) { if err := validateFlags(flags); err != nil { return err } // We parse the plugin full name from the user-provided argument. pluginFullName, err := bufparse.ParseFullName(container.Arg(0)) if err != nil { return appcmd.WrapInvalidArgumentError(err) } commit, err := upload(ctx, container, flags, pluginFullName) if err != nil { return err } // Only one commit is returned. if _, err := fmt.Fprintf(container.Stdout(), "%s\n", commit.PluginKey().String()); err != nil { return syserror.Wrap(err) } return nil } func upload( ctx context.Context, container appext.Container, flags *flags, pluginFullName bufparse.FullName, ) (_ bufplugin.Commit, retErr error) { var plugin bufplugin.Plugin switch { case flags.Binary != "": // We create a local plugin reference to the Wasm binary. var err error plugin, err = bufplugin.NewLocalWasmPlugin( pluginFullName, flags.Binary, nil, // args func() ([]byte, error) { wasmBinary, err := os.ReadFile(flags.Binary) if err != nil { return nil, fmt.Errorf("could not read Wasm binary %q: %w", flags.Binary, err) } return wasmBinary, nil }, ) if err != nil { return nil, err } default: // This should never happen because the flags are validated. return nil, syserror.Newf("--%s must be set", binaryFlagName) } uploader, err := bufcli.NewPluginUploader(container) if err != nil { return nil, err } var options []bufplugin.UploadOption if flags.Create { createPluginVisibility, err := bufplugin.ParsePluginVisibility(flags.CreateVisibility) if err != nil { return nil, err } createPluginType, err := bufplugin.ParsePluginType(flags.CreateType) if err != nil { return nil, err } options = append(options, bufplugin.UploadWithCreateIfNotExist( createPluginVisibility, createPluginType, )) } if len(flags.Labels) > 0 { options = append(options, bufplugin.UploadWithLabels(flags.Labels...)) } if flags.SourceControlURL != "" { options = append(options, bufplugin.UploadWithSourceControlURL(flags.SourceControlURL)) } commits, err := uploader.Upload(ctx, []bufplugin.Plugin{plugin}, options...) if err != nil { return nil, err } if len(commits) != 1 { return nil, syserror.Newf("unexpected number of commits returned from server: %d", len(commits)) } return commits[0], nil } func validateFlags(flags *flags) error { if err := validateLabelFlags(flags); err != nil { return err } if err := validateTypeFlags(flags); err != nil { return err } if err := validateCreateFlags(flags); err != nil { return err } return nil } func validateLabelFlags(flags *flags) error { return validateLabelFlagValues(flags) } func validateTypeFlags(flags *flags) error { var typeFlags []string if flags.Binary != "" { typeFlags = append(typeFlags, binaryFlagName) } if len(typeFlags) > 1 { usedFlagsErrStr := strings.Join( xslices.Map( typeFlags, func(flag string) string { return fmt.Sprintf("--%s", flag) }, ), ", ", ) return appcmd.NewInvalidArgumentErrorf("These flags cannot be used in combination with one another: %s", usedFlagsErrStr) } if len(typeFlags) == 0 { return appcmd.NewInvalidArgumentErrorf("--%s must be set", binaryFlagName) } return nil } func validateLabelFlagValues(flags *flags) error { if slices.Contains(flags.Labels, "") { return appcmd.NewInvalidArgumentErrorf("--%s requires a non-empty string", labelFlagName) } return nil } func validateCreateFlags(flags *flags) error { if flags.Create { if flags.CreateVisibility == "" { return appcmd.NewInvalidArgumentErrorf("--%s must be set if --%s is set", createVisibilityFlagName, createFlagName) } if _, err := bufplugin.ParsePluginVisibility(flags.CreateVisibility); err != nil { return appcmd.WrapInvalidArgumentError(err) } } if flags.Create { if flags.CreateType == "" { return appcmd.NewInvalidArgumentErrorf("--%s must be set if --%s is set", createTypeFlagName, createFlagName) } if _, err := bufplugin.ParsePluginType(flags.CreateType); err != nil { return appcmd.WrapInvalidArgumentError(err) } } return nil } ================================================ FILE: cmd/buf/internal/command/plugin/pluginupdate/pluginupdate.go ================================================ // Copyright 2020-2026 Buf Technologies, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package pluginupdate import ( "context" "errors" "fmt" "buf.build/go/app/appcmd" "buf.build/go/app/appext" "github.com/bufbuild/buf/private/buf/bufcli" "github.com/bufbuild/buf/private/bufpkg/bufplugin" "github.com/bufbuild/buf/private/pkg/syserror" "github.com/spf13/pflag" ) const ( onlyFlagName = "only" ) // NewCommand returns a new Command. func NewCommand( name string, builder appext.SubCommandBuilder, ) *appcmd.Command { flags := newFlags() return &appcmd.Command{ Use: name + " ", Short: "Update pinned remote plugins in a buf.lock", Long: `Fetch the latest digests for the specified plugin references in buf.yaml. The first argument is the directory of the local module to update. Defaults to "." if no argument is specified.`, Args: appcmd.MaximumNArgs(1), Run: builder.NewRunFunc( func(ctx context.Context, container appext.Container) error { return run(ctx, container, flags) }, ), BindFlags: flags.Bind, } } type flags struct { Only []string } func newFlags() *flags { return &flags{} } func (f *flags) Bind(flagSet *pflag.FlagSet) { flagSet.StringSliceVar( &f.Only, onlyFlagName, nil, "The name of the plugin to update. When set, only this plugin is updated. May be provided multiple times", ) // TODO FUTURE: implement _ = flagSet.MarkHidden(onlyFlagName) } func run( ctx context.Context, container appext.Container, flags *flags, ) (retErr error) { dirPath := "." if container.NumArgs() > 0 { dirPath = container.Arg(0) } if len(flags.Only) > 0 { // TODO FUTURE: implement return syserror.Newf("--%s is not implemented", onlyFlagName) } logger := container.Logger() controller, err := bufcli.NewController(container) if err != nil { return err } workspaceDepManager, err := controller.GetWorkspaceDepManager(ctx, dirPath) if err != nil { return err } configuredRemotePluginRefs, err := workspaceDepManager.ConfiguredRemotePluginRefs(ctx) if err != nil { return err } pluginKeyProvider, err := bufcli.NewPluginKeyProvider(container) if err != nil { return err } configuredRemotePluginKeys, err := pluginKeyProvider.GetPluginKeysForPluginRefs( ctx, configuredRemotePluginRefs, bufplugin.DigestTypeP1, ) if err != nil { return err } // Store the existing buf.lock data. existingRemotePluginKeys, err := workspaceDepManager.ExistingBufLockFileRemotePluginKeys(ctx) if err != nil { return err } if configuredRemotePluginKeys == nil && existingRemotePluginKeys == nil { // No new configured remote plugins were found, and no existing buf.lock deps were found, so there // is nothing to update, we can return here. // This ensures we do not create an empty buf.lock when one did not exist in the first // place and we do not need to go through the entire operation of updating non-existent // deps and building the image for tamper-proofing. logger.Warn(fmt.Sprintf("No configured remote plugins were found to update in %q.", dirPath)) return nil } existingDepModuleKeys, err := workspaceDepManager.ExistingBufLockFileDepModuleKeys(ctx) if err != nil { return err } existingRemotePolicyKeys, err := workspaceDepManager.ExistingBufLockFileRemotePolicyKeys(ctx) if err != nil { return err } existingPolicyNameToRemotePluginKeys, err := workspaceDepManager.ExistingBufLockFilePolicyNameToRemotePluginKeys(ctx) if err != nil { return err } // We're about to edit the buf.lock file on disk. If we have a subsequent error, // attempt to revert the buf.lock file. // // TODO FUTURE: We should be able to update the buf.lock file in an in-memory bucket, then do the rebuild, // and if the rebuild is successful, then actually write to disk. It shouldn't even be that much work - just // overlay the new buf.lock file in a union bucket. defer func() { if retErr != nil { retErr = errors.Join(retErr, workspaceDepManager.UpdateBufLockFile( ctx, existingDepModuleKeys, existingRemotePluginKeys, existingRemotePolicyKeys, existingPolicyNameToRemotePluginKeys, )) } }() // Edit the buf.lock file with the updated remote plugins. if err := workspaceDepManager.UpdateBufLockFile(ctx, existingDepModuleKeys, configuredRemotePluginKeys, existingRemotePolicyKeys, existingPolicyNameToRemotePluginKeys); err != nil { return err } return nil } ================================================ FILE: cmd/buf/internal/command/policy/policyprune/policyprune.go ================================================ // Copyright 2020-2026 Buf Technologies, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package policyprune import ( "context" "buf.build/go/app/appcmd" "buf.build/go/app/appext" "buf.build/go/standard/xslices" "github.com/bufbuild/buf/private/buf/bufcli" "github.com/bufbuild/buf/private/buf/bufworkspace" "github.com/bufbuild/buf/private/bufpkg/bufparse" "github.com/bufbuild/buf/private/bufpkg/bufplugin" "github.com/bufbuild/buf/private/bufpkg/bufpolicy" ) // NewCommand returns a new Command. func NewCommand( name string, builder appext.SubCommandBuilder, ) *appcmd.Command { return &appcmd.Command{ Use: name + " ", Short: "Prune unused policies from buf.lock", Long: `Policies that are no longer configured in buf.yaml are removed from the buf.lock file. The first argument is the directory of your buf.yaml configuration file. Defaults to "." if no argument is specified.`, Args: appcmd.MaximumNArgs(1), Run: builder.NewRunFunc( func(ctx context.Context, container appext.Container) error { return run(ctx, container) }, ), } } func run( ctx context.Context, container appext.Container, ) error { dirPath := "." if container.NumArgs() > 0 { dirPath = container.Arg(0) } controller, err := bufcli.NewController(container) if err != nil { return err } workspaceDepManager, err := controller.GetWorkspaceDepManager(ctx, dirPath) if err != nil { return err } configuredRemotePolicyRefs, err := workspaceDepManager.ConfiguredRemotePolicyRefs(ctx) if err != nil { return err } return prune( ctx, xslices.Map( configuredRemotePolicyRefs, func(policyRef bufparse.Ref) string { return policyRef.FullName().String() }, ), workspaceDepManager, ) } func prune( ctx context.Context, bufYAMLBasedRemotePolicyNames []string, workspaceDepManager bufworkspace.WorkspaceDepManager, ) error { bufYAMLRemotePolicyNames := xslices.ToStructMap(bufYAMLBasedRemotePolicyNames) existingRemotePolicyKeys, err := workspaceDepManager.ExistingBufLockFileRemotePolicyKeys(ctx) if err != nil { return err } existingPolicyNameToRemotePluginKeys, err := workspaceDepManager.ExistingBufLockFilePolicyNameToRemotePluginKeys(ctx) if err != nil { return err } var ( prunedBufLockPolicyKeys []bufpolicy.PolicyKey prunedBufLockPolicyNameToRemotePluginKeys = make(map[string][]bufplugin.PluginKey) ) for _, existingRemotePolicyKey := range existingRemotePolicyKeys { // Check if an existing policy key from the buf.lock is configured in the buf.yaml. policyFullNameString := existingRemotePolicyKey.FullName().String() if _, ok := bufYAMLRemotePolicyNames[policyFullNameString]; ok { // If yes, then we keep it for the updated buf.lock. prunedBufLockPolicyKeys = append(prunedBufLockPolicyKeys, existingRemotePolicyKey) existingRemotePluginKeys := existingPolicyNameToRemotePluginKeys[policyFullNameString] prunedBufLockPolicyNameToRemotePluginKeys[policyFullNameString] = existingRemotePluginKeys } } // We keep the existing dep module keys as-is. existingDepModuleKeys, err := workspaceDepManager.ExistingBufLockFileDepModuleKeys(ctx) if err != nil { return err } existingRemotePluginKeys, err := workspaceDepManager.ExistingBufLockFileRemotePluginKeys(ctx) if err != nil { return err } return workspaceDepManager.UpdateBufLockFile(ctx, existingDepModuleKeys, existingRemotePluginKeys, prunedBufLockPolicyKeys, prunedBufLockPolicyNameToRemotePluginKeys) } ================================================ FILE: cmd/buf/internal/command/policy/policypush/policypush.go ================================================ // Copyright 2020-2026 Buf Technologies, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package policypush import ( "bytes" "context" "fmt" "os" "slices" "buf.build/go/app/appcmd" "buf.build/go/app/appext" "github.com/bufbuild/buf/private/buf/bufcli" "github.com/bufbuild/buf/private/bufpkg/bufparse" "github.com/bufbuild/buf/private/bufpkg/bufpolicy" "github.com/bufbuild/buf/private/bufpkg/bufpolicy/bufpolicyconfig" "github.com/bufbuild/buf/private/pkg/syserror" "github.com/google/uuid" "github.com/spf13/pflag" ) const ( labelFlagName = "label" createFlagName = "create" createVisibilityFlagName = "create-visibility" ) // NewCommand returns a new Command. func NewCommand( name string, builder appext.SubCommandBuilder, ) *appcmd.Command { flags := newFlags() return &appcmd.Command{ Use: name + " ", Short: "Push a policy to a registry", Long: `The first argument is the path to the local policy config.`, Args: appcmd.ExactArgs(1), Run: builder.NewRunFunc( func(ctx context.Context, container appext.Container) error { return run(ctx, container, flags) }, ), BindFlags: flags.Bind, } } type flags struct { Labels []string Create bool CreateVisibility string SourceControlURL string } func newFlags() *flags { return &flags{} } func (f *flags) Bind(flagSet *pflag.FlagSet) { bufcli.BindCreateVisibility(flagSet, &f.CreateVisibility, createVisibilityFlagName, createFlagName) flagSet.StringSliceVar( &f.Labels, labelFlagName, nil, "Associate the label with the policies pushed. Can be used multiple times.", ) flagSet.BoolVar( &f.Create, createFlagName, false, fmt.Sprintf( "Create the policy if it does not exist. Defaults to creating a private policy on the BSR if --%s is not set.", createVisibilityFlagName, ), ) } func run( ctx context.Context, container appext.Container, flags *flags, ) (retErr error) { if err := validateFlags(flags); err != nil { return err } commit, err := upload(ctx, container, flags) if err != nil { return err } // Only one commit is returned. if _, err := fmt.Fprintf(container.Stdout(), "%s\n", commit.PolicyKey().String()); err != nil { return syserror.Wrap(err) } return nil } func upload( ctx context.Context, container appext.Container, flags *flags, ) (_ bufpolicy.Commit, retErr error) { var policy bufpolicy.Policy if container.NumArgs() != 1 { // This should never happen because the args length is validated. return nil, syserror.Newf("policy arg must be provided") } policyFilePath := container.Arg(0) // We read the policy YAML file. data, err := os.ReadFile(policyFilePath) if err != nil { return nil, appcmd.NewInvalidArgumentErrorf("could not read policy file %q: %w", policyFilePath, err) } // Parse the policy YAML file to validate it upfront. policyYamlFile, err := bufpolicyconfig.ReadBufPolicyYAMLFile(bytes.NewReader(data), policyFilePath) if err != nil { return nil, appcmd.NewInvalidArgumentErrorf("unable to validate policy file %q: %w", policyFilePath, err) } // We parse the policy full name from the user-provided argument. if policyYamlFile.Name() == "" { return nil, appcmd.NewInvalidArgumentErrorf("policy file %q must have a name", policyFilePath) } policyFullName, err := bufparse.ParseFullName(policyYamlFile.Name()) if err != nil { return nil, appcmd.NewInvalidArgumentErrorf("unable to parse policy full name %q: %w", policyYamlFile.Name(), err) } policy, err = bufpolicy.NewPolicy(policyFilePath, policyFullName, policyFullName.Name(), uuid.Nil, policyYamlFile.PolicyConfig) if err != nil { return nil, fmt.Errorf("unable to create policy from file %q: %w", policyFilePath, err) } uploader, err := bufcli.NewPolicyUploader(container) if err != nil { return nil, err } var options []bufpolicy.UploadOption if flags.Create { createPolicyVisibility, err := bufpolicy.ParsePolicyVisibility(flags.CreateVisibility) if err != nil { return nil, err } options = append(options, bufpolicy.UploadWithCreateIfNotExist( createPolicyVisibility, )) } if len(flags.Labels) > 0 { options = append(options, bufpolicy.UploadWithLabels(flags.Labels...)) } if flags.SourceControlURL != "" { options = append(options, bufpolicy.UploadWithSourceControlURL(flags.SourceControlURL)) } commits, err := uploader.Upload(ctx, []bufpolicy.Policy{policy}, options...) if err != nil { return nil, err } if len(commits) != 1 { return nil, syserror.Newf("unexpected number of commits returned from server: %d", len(commits)) } return commits[0], nil } func validateFlags(flags *flags) error { if err := validateLabelFlags(flags); err != nil { return err } if err := validateCreateFlags(flags); err != nil { return err } return nil } func validateLabelFlags(flags *flags) error { return validateLabelFlagValues(flags) } func validateLabelFlagValues(flags *flags) error { if slices.Contains(flags.Labels, "") { return appcmd.NewInvalidArgumentErrorf("--%s requires a non-empty string", labelFlagName) } return nil } func validateCreateFlags(flags *flags) error { if flags.Create { if flags.CreateVisibility == "" { return appcmd.NewInvalidArgumentErrorf("--%s must be set if --%s is set", createVisibilityFlagName, createFlagName) } if _, err := bufpolicy.ParsePolicyVisibility(flags.CreateVisibility); err != nil { return appcmd.WrapInvalidArgumentError(err) } } return nil } ================================================ FILE: cmd/buf/internal/command/policy/policyupdate/policyupdate.go ================================================ // Copyright 2020-2026 Buf Technologies, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package policyupdate import ( "context" "errors" "fmt" "maps" "buf.build/go/app/appcmd" "buf.build/go/app/appext" "buf.build/go/standard/xslices" "github.com/bufbuild/buf/private/buf/bufcli" "github.com/bufbuild/buf/private/bufpkg/bufparse" "github.com/bufbuild/buf/private/bufpkg/bufplugin" "github.com/bufbuild/buf/private/bufpkg/bufpolicy" "github.com/bufbuild/buf/private/pkg/syserror" "github.com/spf13/pflag" ) const ( onlyFlagName = "only" ) // NewCommand returns a new Command. func NewCommand( name string, builder appext.SubCommandBuilder, ) *appcmd.Command { flags := newFlags() return &appcmd.Command{ Use: name + " ", Short: "Update pinned remote policies in a buf.lock", Long: `Fetch the latest digests for the specified policy references in buf.yaml. The first argument is the directory of the local module to update. Defaults to "." if no argument is specified.`, Args: appcmd.MaximumNArgs(1), Run: builder.NewRunFunc( func(ctx context.Context, container appext.Container) error { return run(ctx, container, flags) }, ), BindFlags: flags.Bind, } } type flags struct { Only []string } func newFlags() *flags { return &flags{} } func (f *flags) Bind(flagSet *pflag.FlagSet) { flagSet.StringSliceVar( &f.Only, onlyFlagName, nil, "The name of the policy to update. When set, only this policy is updated. May be provided multiple times", ) // TODO FUTURE: implement _ = flagSet.MarkHidden(onlyFlagName) } func run( ctx context.Context, container appext.Container, flags *flags, ) (retErr error) { dirPath := "." if container.NumArgs() > 0 { dirPath = container.Arg(0) } if len(flags.Only) > 0 { // TODO FUTURE: implement return syserror.Newf("--%s is not implemented", onlyFlagName) } logger := container.Logger() controller, err := bufcli.NewController(container) if err != nil { return err } workspaceDepManager, err := controller.GetWorkspaceDepManager(ctx, dirPath) if err != nil { return err } configuredRemotePolicyRefs, err := workspaceDepManager.ConfiguredRemotePolicyRefs(ctx) if err != nil { return err } configuredLocalPolicyNameToRemotePluginRefs, err := workspaceDepManager.ConfiguredLocalPolicyNameToRemotePluginRefs(ctx) if err != nil { return err } policyKeyProvider, err := bufcli.NewPolicyKeyProvider(container) if err != nil { return err } configuredRemotePolicyKeys, err := policyKeyProvider.GetPolicyKeysForPolicyRefs( ctx, configuredRemotePolicyRefs, bufpolicy.DigestTypeO1, ) if err != nil { return err } configuredRemotePolicyNameToRemotePluginKeys, err := getPolicyKeyPluginKeysForPolicyKeys( ctx, container, configuredRemotePolicyKeys, ) if err != nil { return err } configuredLocalPolicyNameToRemotePluginKeys, err := getPolicyPluginKeysForPolicyNames( ctx, container, configuredLocalPolicyNameToRemotePluginRefs, ) if err != nil { return err } var configuredPolicyNameToRemotePluginKeys map[string][]bufplugin.PluginKey if policyCount := len(configuredRemotePolicyNameToRemotePluginKeys) + len(configuredLocalPolicyNameToRemotePluginKeys); policyCount > 0 { configuredPolicyNameToRemotePluginKeys = make(map[string][]bufplugin.PluginKey, policyCount) maps.Copy(configuredPolicyNameToRemotePluginKeys, configuredRemotePolicyNameToRemotePluginKeys) maps.Copy(configuredPolicyNameToRemotePluginKeys, configuredLocalPolicyNameToRemotePluginKeys) } // Store the existing buf.lock data. existingRemotePolicyKeys, err := workspaceDepManager.ExistingBufLockFileRemotePolicyKeys(ctx) if err != nil { return err } existingPolicyNameToRemotePluginKeys, err := workspaceDepManager.ExistingBufLockFilePolicyNameToRemotePluginKeys(ctx) if err != nil { return err } if len(configuredRemotePolicyKeys) == 0 && len(configuredPolicyNameToRemotePluginKeys) == 0 && len(existingRemotePolicyKeys) == 0 && len(existingPolicyNameToRemotePluginKeys) == 0 { // No new configured remote plugins were found, and no existing buf.lock deps were found, so there // is nothing to update, we can return here. // This ensures we do not create an empty buf.lock when one did not exist in the first // place and we do not need to go through the entire operation of updating non-existent // deps and building the image for tamper-proofing. logger.Warn(fmt.Sprintf("No configured remote policies were found to update in %q.", dirPath)) return nil } existingDepModuleKeys, err := workspaceDepManager.ExistingBufLockFileDepModuleKeys(ctx) if err != nil { return err } existingRemotePluginKeys, err := workspaceDepManager.ExistingBufLockFileRemotePluginKeys(ctx) if err != nil { return err } // We're about to edit the buf.lock file on disk. If we have a subsequent error, // attempt to revert the buf.lock file. // // TODO FUTURE: We should be able to update the buf.lock file in an in-memory bucket, then do the rebuild, // and if the rebuild is successful, then actually write to disk. It shouldn't even be that much work - just // overlay the new buf.lock file in a union bucket. defer func() { if retErr != nil { retErr = errors.Join(retErr, workspaceDepManager.UpdateBufLockFile( ctx, existingDepModuleKeys, existingRemotePluginKeys, existingRemotePolicyKeys, existingPolicyNameToRemotePluginKeys, )) } }() // Edit the buf.lock file with the updated remote plugins. if err := workspaceDepManager.UpdateBufLockFile(ctx, existingDepModuleKeys, existingRemotePluginKeys, configuredRemotePolicyKeys, configuredPolicyNameToRemotePluginKeys); err != nil { return err } return nil } func getPolicyKeyPluginKeysForPolicyKeys( ctx context.Context, container appext.Container, policyKeys []bufpolicy.PolicyKey, ) (map[string][]bufplugin.PluginKey, error) { if len(policyKeys) == 0 { return nil, nil } policyDataProvider, err := bufcli.NewPolicyDataProvider(container) if err != nil { return nil, err } pluginKeyProvider, err := bufcli.NewPluginKeyProvider(container) if err != nil { return nil, err } policyDatas, err := policyDataProvider.GetPolicyDatasForPolicyKeys(ctx, policyKeys) if err != nil { return nil, err } policyNameToRemotePluginKeys := make(map[string][]bufplugin.PluginKey) for _, policyData := range policyDatas { policyConfig, err := policyData.Config() if err != nil { return nil, err } pluginConfigs := policyConfig.PluginConfigs() pluginRefs, err := xslices.MapError(pluginConfigs, func(pluginConfig bufpolicy.PluginConfig) (bufparse.Ref, error) { pluginRef := pluginConfig.Ref() if pluginRef == nil { return nil, fmt.Errorf("plugin config %q does not have a valid ref", pluginConfig.Name()) } return pluginRef, nil }) if err != nil { return nil, fmt.Errorf("failed to get plugin refs for policy %q: %w", policyData.PolicyKey(), err) } remotePluginKeys, err := pluginKeyProvider.GetPluginKeysForPluginRefs( ctx, pluginRefs, bufplugin.DigestTypeP1, ) if err != nil { return nil, err } policyName := policyData.PolicyKey().FullName().String() if len(remotePluginKeys) > 0 { policyNameToRemotePluginKeys[policyName] = remotePluginKeys } } return policyNameToRemotePluginKeys, nil } func getPolicyPluginKeysForPolicyNames( ctx context.Context, container appext.Container, localPolicyNameToRemotePluginRefs map[string][]bufparse.Ref, ) (map[string][]bufplugin.PluginKey, error) { if len(localPolicyNameToRemotePluginRefs) == 0 { return nil, nil } pluginKeyProvider, err := bufcli.NewPluginKeyProvider(container) if err != nil { return nil, err } policyNameToRemotePluginKeys := make(map[string][]bufplugin.PluginKey) for policyName, pluginRefs := range localPolicyNameToRemotePluginRefs { remotePluginKeys, err := pluginKeyProvider.GetPluginKeysForPluginRefs( ctx, pluginRefs, bufplugin.DigestTypeP1, ) if err != nil { return nil, err } if len(remotePluginKeys) > 0 { policyNameToRemotePluginKeys[policyName] = remotePluginKeys } } return policyNameToRemotePluginKeys, nil } ================================================ FILE: cmd/buf/internal/command/push/push.go ================================================ // Copyright 2020-2026 Buf Technologies, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package push import ( "context" "errors" "fmt" "slices" "strings" "buf.build/go/app" "buf.build/go/app/appcmd" "buf.build/go/app/appext" "buf.build/go/standard/xslices" "buf.build/go/standard/xstrings" "github.com/bufbuild/buf/private/buf/bufcli" "github.com/bufbuild/buf/private/buf/bufctl" "github.com/bufbuild/buf/private/buf/buffetch" "github.com/bufbuild/buf/private/buf/bufworkspace" "github.com/bufbuild/buf/private/bufpkg/bufanalysis" "github.com/bufbuild/buf/private/bufpkg/bufmodule" "github.com/bufbuild/buf/private/pkg/git" "github.com/bufbuild/buf/private/pkg/syserror" "github.com/bufbuild/buf/private/pkg/uuidutil" "github.com/spf13/pflag" ) const ( labelFlagName = "label" errorFormatFlagName = "error-format" disableSymlinksFlagName = "disable-symlinks" createFlagName = "create" createVisibilityFlagName = "create-visibility" createDefaultLabelFlagName = "create-default-label" sourceControlURLFlagName = "source-control-url" gitMetadataFlagName = "git-metadata" excludeUnnamedFlagName = "exclude-unnamed" // All deprecated. tagFlagName = "tag" tagFlagShortName = "t" draftFlagName = "draft" branchFlagName = "branch" gitOriginRemote = "origin" ) var ( useLabelInstead = fmt.Sprintf("use --%s instead.", labelFlagName) ) // NewCommand returns a new Command. func NewCommand( name string, builder appext.SubCommandBuilder, ) *appcmd.Command { flags := newFlags() return &appcmd.Command{ Use: name + " ", Short: "Push to a registry", Long: bufcli.GetSourceLong(`the source to push`), Args: appcmd.MaximumNArgs(1), Run: builder.NewRunFunc( func(ctx context.Context, container appext.Container) error { return run(ctx, container, flags) }, ), BindFlags: flags.Bind, } } type flags struct { Tags []string Branch string Draft string Labels []string ErrorFormat string DisableSymlinks bool Create bool CreateVisibility string CreateDefaultLabel string SourceControlURL string ExcludeUnnamed bool GitMetadata bool // special InputHashtag string } func newFlags() *flags { return &flags{} } func (f *flags) Bind(flagSet *pflag.FlagSet) { bufcli.BindInputHashtag(flagSet, &f.InputHashtag) bufcli.BindDisableSymlinks(flagSet, &f.DisableSymlinks, disableSymlinksFlagName) bufcli.BindCreateVisibility(flagSet, &f.CreateVisibility, createVisibilityFlagName, createFlagName) flagSet.StringSliceVar( &f.Labels, labelFlagName, nil, "Associate the label with the modules pushed. Can be used multiple times.", ) flagSet.StringVar( &f.ErrorFormat, errorFormatFlagName, "text", fmt.Sprintf( "The format for build errors printed to stderr. Must be one of %s", xstrings.SliceToString(bufanalysis.AllFormatStrings), ), ) flagSet.BoolVar( &f.Create, createFlagName, false, fmt.Sprintf( "Create the module if it does not exist. Defaults to creating a private module if --%s is not set.", createVisibilityFlagName, ), ) flagSet.StringVar( &f.CreateDefaultLabel, createDefaultLabelFlagName, "", `The module's default label setting, if created. If this is not set, then the module will be created with the default label "main".`, ) flagSet.StringVar( &f.SourceControlURL, sourceControlURLFlagName, "", "The URL for viewing the source code of the pushed modules (e.g. the specific commit in source control).", ) flagSet.BoolVar( &f.GitMetadata, gitMetadataFlagName, false, fmt.Sprintf( `Uses the Git source control state to set flag values. If this flag is set, we will use the following values for your flags: --%s to /// (e.g. https://github.com/acme/weather/commit/ffac537e6cbbf934b08745a378932722df287a53). --%s for each Git tag and branch pointing to the currently checked out commit. You can set additional labels using --%s with this flag. --%s to the Git default branch (e.g. main) - this is only in effect if --%s is also set. The source control URL and default branch is based on the required Git remote %q. This flag is only compatible with checkouts of Git source repositories. If you set the --%s flag and/or --%s flag yourself, then the value(s) will be used instead and the information will not be derived from the Git source control state.`, sourceControlURLFlagName, labelFlagName, labelFlagName, createDefaultLabelFlagName, createFlagName, gitOriginRemote, sourceControlURLFlagName, createDefaultLabelFlagName, ), ) flagSet.BoolVar( &f.ExcludeUnnamed, excludeUnnamedFlagName, false, "Only push named modules to the BSR. Named modules must not have any unnamed dependencies.", ) flagSet.StringSliceVarP(&f.Tags, tagFlagName, tagFlagShortName, nil, useLabelInstead) _ = flagSet.MarkHidden(tagFlagName) _ = flagSet.MarkHidden(tagFlagShortName) _ = flagSet.MarkDeprecated( tagFlagName, fmt.Sprintf( `%s Replace "buf push --tag " with "buf push --label --label " ( and are equivalent in this case). You can also use "buf registry commit add-label --label