name: crud-proto-gen
description: PowerX Proto 生成规则(buf、go_package、Make 目标)。
PowerX CRUD Proto Gen
步骤
- 打开
本文件内嵌规则。
- 按规则执行实现/校对。
- 完成后按核对清单验收。
核对点
- 与 PowerX 当前代码结构、路径与命名一致。
- 仅在传输层/契约层做职责内改动,不跨层越界。
规则(内嵌)
proto_gen.yaml
kind: ruleset
name: proto_gen
version: 1.0.2
owner: powerx
status: stable
meta:
intent: >
统一 Proto 的组织与生成规范:使用 buf 管理、版本化与生成;go_package 与生成路径一致;
Make 目标必须存在;生成物输出到 api/grpc/gen 下并纳入 .gitignore(或采用只读提交策略)。
references:
- crud_grpc.yaml
- transport_grpc.yaml
scope:
applies_to:
- "api/grpc/**/*.proto"
- "api/grpc/contracts/buf.yaml"
- "api/grpc/contracts/buf.gen.yaml"
- "buf.work.yaml"
- "Makefile"
- ".gitignore"
principles:
- 目录规范:源 proto 放在 api/grpc/**;生成代码放在 api/grpc/gen/**。
- 工具规范:使用 buf(lint/breaking/生成),不直接手写 protoc 命令(由 buf.gen.yaml 驱动)。
- 包路径一致:每个 proto 必须设置 option go_package,且与生成前缀 github.com/ArtisanCloud/PowerX/api/grpc/gen 保持一致。
- 生成产物可清理:必须提供 proto-gen / proto-lint / proto-clean 的 Make 目标。
- 版本可控:api/grpc/contracts/buf.yaml 指定 lint/breaking 规则;api/grpc/contracts/buf.gen.yaml 锁定插件版本(尽量锁到 minor)。
- 产物纳管:默认在 .gitignore 忽略 api/grpc/gen;如需提交产物,需锁定插件版本并允许提交。
- Workspace:使用 buf.work.yaml 管理多目录输入,禁止在 buf.yaml 使用 build.roots 等 v0 字段。
checks:
# ===== 基本文件存在 =====
- id: proto.files.required
level: error
when: { glob: "api/grpc/**/*.proto" }
assert:
- must_exist_glob: "api/grpc/**/*.proto"
- id: buf.configs.exist
level: error
when:
any_of:
- file: "api/grpc/contracts/buf.yaml"
- file: "api/grpc/contracts/buf.gen.yaml"
assert:
- file_exists: "api/grpc/contracts/buf.yaml"
- file_exists: "api/grpc/contracts/buf.gen.yaml"
- id: buf.workspace.exists
level: error
when: { file: "buf.work.yaml" }
assert:
- file_exists: "buf.work.yaml"
- must_contain_regex: "^version:\\s*v1\\b"
- must_contain: "directories:"
message: "必须使用 workspace(buf.work.yaml)管理多目录输入。"
# ===== go_package 必须存在且与生成路径前缀一致 =====
- id: proto.go_package.set
level: error
when: { glob: "api/grpc/**/*.proto" }
assert:
- must_contain_regex: "option\\s+go_package\\s*=\\s*\"[^\"]+\";"
- id: proto.go_package.path_match
level: warn
when: { glob: "api/grpc/**/*.proto" }
assert:
- should_contain_regex: "option go_package = \"github.com/ArtisanCloud/PowerX/api/grpc/gen/.+;[a-zA-Z0-9_]+\";"
message: "建议 go_package 使用 github.com/ArtisanCloud/PowerX/api/grpc/gen/<domain>;pkgname 前缀"
# ===== buf.yaml 的 lint / breaking 规则(不再要求 build.*)=====
- id: buf.yaml.rules
level: error
when: { file: "api/grpc/contracts/buf.yaml" }
assert:
- must_contain_regex: "^version:\\s*v1\\b"
- must_contain: "lint:"
- must_contain: "breaking:"
- must_contain_any:
- "use: [DEFAULT]"
- "use:\n - DEFAULT"
- must_not_contain_regex: "\\bbuild\\s*:"
- must_not_contain_regex: "\\bbuild\\.roots\\b"
# ===== buf.gen.yaml 的 go/grpc 插件与输出路径 =====
- id: buf.gen.outputs
level: error
when: { file: "api/grpc/contracts/buf.gen.yaml" }
assert:
- must_contain_regex: "plugin:\\s*buf.build/protocolbuffers/go(:|\\b)"
- must_contain_regex: "plugin:\\s*buf.build/grpc/go(:|\\b)"
- must_contain_regex: "out:\\s*api/grpc/gen"
- must_contain: "opt:\n - paths=source_relative"
- must_contain: "managed:"
- must_contain: "go_package_prefix:"
- must_contain: "default: github.com/ArtisanCloud/PowerX/api/grpc/gen"
# ===== Makefile 目标 =====
- id: make.targets
level: error
when: { file: "Makefile" }
assert:
- must_contain_regex: "^proto-gen:"
- must_contain_regex: "^proto-lint:"
- must_contain_regex: "^proto-clean:"
# ===== 产物纳管:默认忽略 api/grpc/gen =====
- id: gitignore.gen
level: warn
when: { file: ".gitignore" }
assert:
- should_contain_any:
- "/api/grpc/gen/"
- "api/grpc/gen/"
acceptance:
checklist:
- "[ ] 源 proto 位于 api/grpc/**;api/grpc/contracts/buf.yaml 与 api/grpc/contracts/buf.gen.yaml 存在"
- "[ ] 每个 proto 设置了 option go_package,且前缀为 github.com/ArtisanCloud/PowerX/api/grpc/gen/**"
- "[ ] 使用 workspace:仓库根存在 buf.work.yaml,且目录收纳正确"
- "[ ] buf.yaml 启用了 lint/breaking(DEFAULT),且未使用 build.*"
- "[ ] buf.gen.yaml 输出到 api/grpc/gen,且 paths=source_relative;managed.go_package_prefix.default=github.com/ArtisanCloud/PowerX/api/grpc/gen"
- "[ ] Makefile 提供 proto-gen / proto-lint / proto-clean"
- "[ ] api/grpc/gen 已在 .gitignore(或锁定插件版本并允许提交产物)"
templates:
buf_yaml: |
# api/grpc/contracts/buf.yaml
version: v1
name: buf.build/powerx/core
lint:
use:
- DEFAULT
breaking:
use:
- FILE
ignore_unstable_packages: true
buf_gen_yaml: |
# api/grpc/contracts/buf.gen.yaml
version: v1
plugins:
- plugin: buf.build/protocolbuffers/go:v1.33.0
out: api/grpc/gen
opt:
- paths=source_relative
- plugin: buf.build/grpc/go:v1.3.0
out: api/grpc/gen
opt:
- paths=source_relative
managed:
enabled: true
go_package_prefix:
default: github.com/ArtisanCloud/PowerX/api/grpc/gen
except:
- buf.build/googleapis/googleapis
- buf.build/protocolbuffers/wellknowntypes
makefile_targets: |
# Makefile(片段)
.PHONY: proto-gen proto-lint proto-clean
# 建议在仓库根执行(buf 会向上查找 buf.work.yaml)
proto-gen:
\tbuf generate --template api/grpc/contracts/buf.gen.yaml --path api/grpc
proto-lint:
\tbuf lint --config api/grpc/contracts/buf.yaml && buf breaking --against '.git#branch=main' --config api/grpc/contracts/buf.yaml || true
proto-clean:
\trm -rf api/grpc/gen/*
proto_example: |
// api/grpc/media/asset.proto
syntax = "proto3";
package powerx.media.v1;
option go_package = "github.com/ArtisanCloud/PowerX/api/grpc/gen/powerx/media/v1;mediav1";
import "google/protobuf/empty.proto";
import "google/protobuf/timestamp.proto";
message Pagination {
int64 total = 1;
int32 page = 2;
int32 page_size = 3;
int32 pages = 4;
}
message MediaAsset {
string id = 1;
uint64 tenant_id = 2;
string name = 3;
string code = 4;
string meta_json = 5;
int32 status = 6;
google.protobuf.Timestamp created_at = 7;
google.protobuf.Timestamp updated_at = 8;
}
message GetMediaAssetRequest { string id = 1; }
message MediaAssetResponse { MediaAsset data = 1; }
service MediaAssetService {
rpc GetMediaAsset(GetMediaAssetRequest) returns (MediaAssetResponse);
}
gitignore_snippet: |
# .gitignore(片段)
/api/grpc/gen/