diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml
index 258ac67..e612451 100644
--- a/.github/workflows/lint.yml
+++ b/.github/workflows/lint.yml
@@ -1,6 +1,10 @@
name: Lint
-on: [push]
+on:
+ push:
+ branches: [ main ]
+ pull_request:
+ branches: [ main ]
jobs:
lint:
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 3ebd241..0a2f984 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -22,4 +22,4 @@ jobs:
version: latest
args: release --clean
env:
- GITHUB_TOKEN: ${{ secrets.TOKEN }}
+ GITHUB_TOKEN: ${{ secrets.RELEASER_TOKEN }}
diff --git a/.goreleaser.yaml b/.goreleaser.yaml
index 4d28f2f..3a8a19d 100644
--- a/.goreleaser.yaml
+++ b/.goreleaser.yaml
@@ -15,17 +15,6 @@ builds:
- -X 'github.com/pubgo/funk/version.project=protobuild'
- -X 'github.com/pubgo/funk/version.buildTime={{ .CommitDate }}'
- -X 'github.com/pubgo/funk/version.commitID={{ .ShortCommit }}'
- - main: ./cmd/protoc-gen-retag/main.go
- id: protoc-gen-retag
- binary: protoc-gen-retag
- skip: false
- goos:
- - linux
- - darwin
- - windows
- goarch:
- - amd64
- - arm64
archives:
- name_template: "{{ .Binary }}-{{ .Tag }}-{{ .Os }}-{{ .Arch }}"
format: binary
diff --git a/.version/VERSION b/.version/VERSION
index a2588c5..8308b63 100644
--- a/.version/VERSION
+++ b/.version/VERSION
@@ -1 +1 @@
-v0.0.29
+v0.1.1
diff --git a/README.md b/README.md
index 44b7202..d235f24 100644
--- a/README.md
+++ b/README.md
@@ -17,6 +17,9 @@
- ⚙️ **Configuration-driven** - YAML-based project configuration
- 📊 **Progress Display** - Visual progress bars and detailed error messages
- 🗑️ **Cache Management** - Clean and manage dependency cache
+- 🌐 **Web UI** - Visual configuration editor with proto file browser
+- 🏥 **Environment Check** - Doctor command to diagnose development environment
+- 🎯 **Project Initialization** - Quick project setup with templates
## Installation
@@ -71,8 +74,14 @@ protobuild gen
| `format -w` | Format and write changes to files |
| `format --diff` | Show diff of formatting changes |
| `format --builtin` | Use builtin formatter instead of buf |
+| `web` | Start web-based configuration UI |
+| `web --port 9090` | Start web UI on custom port |
| `clean` | Clean dependency cache |
| `clean --dry-run` | Show what would be cleaned without deleting |
+| `init` | Initialize a new protobuild project |
+| `init --template grpc` | Initialize with specific template (basic, grpc, minimal) |
+| `doctor` | Check development environment and dependencies |
+| `doctor --fix` | Auto-install missing Go plugins |
| `version` | Show version information |
## Configuration
@@ -238,6 +247,69 @@ protobuild format --builtin
protobuild format -w proto/ api/
```
+### Web Configuration UI
+
+```bash
+# Start web UI on default port (8080)
+protobuild web
+
+# Start web UI on custom port
+protobuild web --port 9090
+```
+
+The web interface provides:
+- 📝 Visual configuration editor
+- 📦 Dependency management
+- 🔌 Plugin configuration
+- 🚀 One-click build, lint, format operations
+- 📄 Real-time YAML preview
+- 📊 Project statistics dashboard
+- 🔍 Proto file browser with syntax highlighting
+- 📚 Configuration examples reference
+
+### Initialize New Project
+
+```bash
+# Interactive initialization
+protobuild init
+
+# Use specific template
+protobuild init --template basic # Basic Go + gRPC project
+protobuild init --template grpc # Full gRPC-Gateway project
+protobuild init --template minimal # Minimal configuration
+
+# Specify output directory
+protobuild init -o ./my-project
+```
+
+### Check Development Environment
+
+```bash
+# Diagnose environment issues
+protobuild doctor
+
+# Auto-install missing Go plugins
+protobuild doctor --fix
+```
+
+Example output:
+```
+🏥 Protobuild Doctor
+
+ Checking development environment...
+
+ ✅ protoc installed (v25.1)
+ ✅ protoc-gen-go installed
+ ✅ protoc-gen-go-grpc installed
+ ✅ buf installed (v1.28.1)
+ ✅ api-linter installed
+ ✅ go installed (go1.21.5)
+ ✅ Configuration protobuf.yaml found
+ ⚠️ Vendor directory not found (run 'protobuild vendor')
+
+ ✅ Environment check passed!
+```
+
### Force Vendor Update
```bash
@@ -339,7 +411,11 @@ protobuild
│ │ └── yaml_types.go # YAML type definitions
│ ├── format/ # Proto file formatting (builtin)
│ ├── formatcmd/ # Format command (buf integration)
-│ └── linters/ # AIP linting rules
+│ ├── linters/ # AIP linting rules
+│ └── webcmd/ # Web configuration UI
+│ ├── cmd.go # Web command entry
+│ ├── server.go # HTTP server and API
+│ └── templates/ # HTML templates (Alpine.js + Tailwind)
└── internal/
├── depresolver/ # Multi-source dependency resolver
├── modutil/ # Go module utilities
@@ -355,6 +431,20 @@ protobuild
- [Multi-Source Dependencies](./docs/MULTI_SOURCE_DEPS.md) - Design document for multi-source dependency resolution
- [Design Document](./docs/DESIGN.md) - Architecture and design documentation
+## Roadmap
+
+Upcoming features planned for future releases:
+
+| Feature | Description | Status |
+|---------|-------------|--------|
+| 🔗 **Dependency Graph** | Visualize proto file import dependencies | Planned |
+| ⚠️ **Breaking Change Detection** | Detect incompatible changes between versions | Planned |
+| 📚 **API Documentation Generator** | Auto-generate Markdown/HTML docs from proto comments | Planned |
+| 🎭 **Mock Server** | Auto-start mock gRPC/HTTP server for testing | Planned |
+| 📝 **Proto Templates** | Quick generation of common proto patterns (CRUD, pagination) | Planned |
+| 📊 **Field Statistics** | Analyze field naming conventions and type distribution | Planned |
+| ✏️ **Online Editor** | Edit proto files directly in Web UI | Planned |
+
## License
[MIT License](LICENSE)
diff --git a/README_CN.md b/README_CN.md
index de06146..645803b 100644
--- a/README_CN.md
+++ b/README_CN.md
@@ -17,6 +17,9 @@
- ⚙️ **配置驱动** - 基于 YAML 的项目配置
- 📊 **进度显示** - 可视化进度条和详细错误信息
- 🗑️ **缓存管理** - 清理和管理依赖缓存
+- 🌐 **Web 界面** - 可视化配置编辑器,支持 Proto 文件浏览
+- 🏥 **环境诊断** - Doctor 命令检查开发环境配置
+- 🎯 **项目初始化** - 快速项目设置,支持多种模板
## 安装
@@ -72,8 +75,14 @@ protobuild gen
| `format --diff` | 显示格式化差异 |
| `format --exit-code` | 需要格式化时返回错误码(CI 适用)|
| `format --builtin` | 使用内置格式化器 |
+| `web` | 启动 Web 配置管理界面 |
+| `web --port 9090` | 指定端口启动 Web 界面 |
| `clean` | 清理依赖缓存 |
| `clean --dry-run` | 预览将被清理的内容 |
+| `init` | 初始化新的 protobuild 项目 |
+| `init --template grpc` | 使用指定模板初始化(basic、grpc、minimal)|
+| `doctor` | 检查开发环境和依赖配置 |
+| `doctor --fix` | 自动安装缺失的 Go 插件 |
| `version` | 显示版本信息 |
## 配置说明
@@ -239,6 +248,69 @@ protobuild format --builtin
protobuild format -w proto/ api/
```
+### Web 配置管理界面
+
+```bash
+# 在默认端口 (8080) 启动 Web 界面
+protobuild web
+
+# 在指定端口启动 Web 界面
+protobuild web --port 9090
+```
+
+Web 界面提供:
+- 📝 可视化配置编辑器
+- 📦 依赖管理
+- 🔌 插件配置
+- 🚀 一键执行构建、检查、格式化等操作
+- 📄 实时 YAML 配置预览
+- 📊 项目统计仪表盘
+- 🔍 Proto 文件浏览器(支持语法高亮)
+- 📚 配置示例参考
+
+### 初始化新项目
+
+```bash
+# 交互式初始化
+protobuild init
+
+# 使用指定模板
+protobuild init --template basic # 基础 Go + gRPC 项目
+protobuild init --template grpc # 完整 gRPC-Gateway 项目
+protobuild init --template minimal # 最小化配置
+
+# 指定输出目录
+protobuild init -o ./my-project
+```
+
+### 检查开发环境
+
+```bash
+# 诊断环境问题
+protobuild doctor
+
+# 自动安装缺失的 Go 插件
+protobuild doctor --fix
+```
+
+输出示例:
+```
+🏥 Protobuild Doctor
+
+ 正在检查开发环境...
+
+ ✅ protoc 已安装 (v25.1)
+ ✅ protoc-gen-go 已安装
+ ✅ protoc-gen-go-grpc 已安装
+ ✅ buf 已安装 (v1.28.1)
+ ✅ api-linter 已安装
+ ✅ go 已安装 (go1.21.5)
+ ✅ 配置文件 已找到 protobuf.yaml
+ ⚠️ Vendor 目录 未找到(请运行 'protobuild vendor')
+
+ ✅ 环境检查通过!
+```
+
### 强制更新 Vendor
```bash
@@ -330,6 +402,20 @@ plugins:
- [多源依赖设计](./docs/MULTI_SOURCE_DEPS.md) - 多源依赖解析设计文档
- [设计文档](./docs/DESIGN_CN.md) - 架构和设计文档
+## 路线图
+
+以下是计划在未来版本中实现的功能:
+
+| 功能 | 描述 | 状态 |
+|------|------|------|
+| 🔗 **依赖关系图** | 可视化 proto 文件的 import 依赖关系 | 计划中 |
+| ⚠️ **Breaking Change 检测** | 检测版本间的不兼容变更 | 计划中 |
+| 📚 **API 文档生成** | 从 proto 注释自动生成 Markdown/HTML 文档 | 计划中 |
+| 🎭 **Mock 服务器** | 自动启动用于测试的 mock gRPC/HTTP 服务器 | 计划中 |
+| 📝 **Proto 模板** | 快速生成常用 proto 模式(CRUD、分页等)| 计划中 |
+| 📊 **字段统计分析** | 分析字段命名规范和类型分布 | 计划中 |
+| ✏️ **在线编辑器** | 在 Web 界面直接编辑 proto 文件 | 计划中 |
+
## 项目架构
```
@@ -346,7 +432,11 @@ protobuild
│ │ └── yaml_types.go # YAML 类型定义
│ ├── format/ # Proto 文件格式化(内置)
│ ├── formatcmd/ # 格式化命令(buf 集成)
-│ └── linters/ # AIP 检查规则
+│ ├── linters/ # AIP 检查规则
+│ └── webcmd/ # Web 配置管理界面
+│ ├── cmd.go # Web 命令入口
+│ ├── server.go # HTTP 服务器和 API
+│ └── templates/ # HTML 模板 (Alpine.js + Tailwind)
└── internal/
├── depresolver/ # 多源依赖解析器
├── modutil/ # Go 模块工具
diff --git a/Taskfile.yml b/Taskfile.yml
index 9bc80ff..7ba7b60 100644
--- a/Taskfile.yml
+++ b/Taskfile.yml
@@ -128,3 +128,7 @@ tasks:
- task: lint
- task: test
- task: build
+ web:
+ desc: Run web server
+ cmds:
+ - go run -v *.go web
diff --git a/cmd/protobuild/cmd.go b/cmd/protobuild/cmd.go
index 6da1d2e..f068b75 100644
--- a/cmd/protobuild/cmd.go
+++ b/cmd/protobuild/cmd.go
@@ -17,6 +17,7 @@ import (
"github.com/pubgo/funk/running"
"github.com/pubgo/protobuild/cmd/formatcmd"
"github.com/pubgo/protobuild/cmd/linters"
+ "github.com/pubgo/protobuild/cmd/webcmd"
"github.com/pubgo/protobuild/internal/shutil"
"github.com/pubgo/protobuild/internal/typex"
"github.com/pubgo/redant"
@@ -72,6 +73,8 @@ func Main(ver string) *redant.Command {
},
Handler: handleStdinPlugin,
Children: typex.Commands{
+ newInitCommand(),
+ newDoctorCommand(),
newGenCommand(),
newVendorCommand(&force, &update),
newInstallCommand(&force),
@@ -79,6 +82,7 @@ func Main(ver string) *redant.Command {
newFormatCommand(),
newDepsCommand(),
newCleanCommand(&dryRun),
+ webcmd.New(&protoCfg),
newVersionCommand(),
},
}
@@ -201,7 +205,7 @@ func installPlugin(plg string, force bool) {
slog.Error("command not found", slog.Any("name", plgName))
}
- if err == nil && !globalCfg.changed && !force {
+ if err == nil && !globalCfg.Changed && !force {
slog.Info("no changes", slog.Any("path", path))
return
}
@@ -256,7 +260,8 @@ func newLintCommand(cliArgs *linters.CliArgs, options typex.Options) *redant.Com
}
includes := lo.Uniq(append(globalCfg.Includes, globalCfg.Vendor))
- if err := linters.Linter(cliArgs, globalCfg.Linter, includes, protoFiles); err != nil {
+ linterCfg := toLinterConfig(globalCfg.Linter)
+ if err := linters.Linter(cliArgs, linterCfg, includes, protoFiles); err != nil {
return err
}
}
diff --git a/cmd/protobuild/cmd_doctor.go b/cmd/protobuild/cmd_doctor.go
new file mode 100644
index 0000000..615478c
--- /dev/null
+++ b/cmd/protobuild/cmd_doctor.go
@@ -0,0 +1,399 @@
+package protobuild
+
+import (
+ "context"
+ "fmt"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "runtime"
+ "strings"
+
+ "github.com/pubgo/funk/recovery"
+ "github.com/pubgo/protobuild/internal/typex"
+ "github.com/pubgo/redant"
+)
+
+// checkItem represents a single environment check.
+type checkItem struct {
+ Name string
+ Description string
+ Check func() checkResult
+}
+
+// checkResult represents the result of a check.
+type checkResult struct {
+ OK bool
+ Message string
+ Help string
+}
+
+// newDoctorCommand creates the doctor command.
+func newDoctorCommand() *redant.Command {
+ var fix bool
+
+ return &redant.Command{
+ Use: "doctor",
+ Short: "检查开发环境配置",
+ Options: typex.Options{
+ redant.Option{
+ Flag: "fix",
+ Description: "尝试自动修复问题",
+ Value: redant.BoolOf(&fix),
+ },
+ },
+ Handler: func(ctx context.Context, inv *redant.Invocation) error {
+ defer recovery.Exit()
+ return runDoctor(fix)
+ },
+ }
+}
+
+// runDoctor executes the doctor command logic.
+func runDoctor(fix bool) error {
+ fmt.Println("🩺 Protobuild 环境检查")
+ fmt.Println("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━")
+ fmt.Println()
+
+ checks := []checkItem{
+ {
+ Name: "protoc",
+ Description: "Protocol Buffers 编译器",
+ Check: checkProtoc,
+ },
+ {
+ Name: "protoc-gen-go",
+ Description: "Go Protobuf 插件",
+ Check: checkProtocGenGo,
+ },
+ {
+ Name: "protoc-gen-go-grpc",
+ Description: "Go gRPC 插件",
+ Check: checkProtocGenGoGrpc,
+ },
+ {
+ Name: "buf",
+ Description: "Buf CLI (可选,用于格式化)",
+ Check: checkBuf,
+ },
+ {
+ Name: "api-linter",
+ Description: "API Linter (可选,用于代码检查)",
+ Check: checkApiLinter,
+ },
+ {
+ Name: "go",
+ Description: "Go 编译器",
+ Check: checkGo,
+ },
+ {
+ Name: "config",
+ Description: "项目配置文件",
+ Check: checkConfig,
+ },
+ {
+ Name: "vendor",
+ Description: "Proto 依赖目录",
+ Check: checkVendor,
+ },
+ }
+
+ var issues []checkItem
+ var warnings []checkItem
+
+ for _, item := range checks {
+ result := item.Check()
+ printCheckResult(item.Name, item.Description, result)
+
+ if !result.OK {
+ if isRequired(item.Name) {
+ issues = append(issues, item)
+ } else {
+ warnings = append(warnings, item)
+ }
+ }
+ }
+
+ fmt.Println()
+ fmt.Println("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━")
+
+ if len(issues) == 0 && len(warnings) == 0 {
+ fmt.Println("✅ 所有检查通过!环境配置正确。")
+ return nil
+ }
+
+ if len(issues) > 0 {
+ fmt.Printf("❌ 发现 %d 个问题需要修复:\n", len(issues))
+ for _, item := range issues {
+ result := item.Check()
+ fmt.Printf(" • %s: %s\n", item.Name, result.Message)
+ if result.Help != "" {
+ fmt.Printf(" 💡 %s\n", result.Help)
+ }
+ }
+ }
+
+ if len(warnings) > 0 {
+ fmt.Printf("⚠️ 发现 %d 个可选组件未安装:\n", len(warnings))
+ for _, item := range warnings {
+ result := item.Check()
+ fmt.Printf(" • %s: %s\n", item.Name, result.Message)
+ if result.Help != "" {
+ fmt.Printf(" 💡 %s\n", result.Help)
+ }
+ }
+ }
+
+ if fix && len(issues) > 0 {
+ fmt.Println("\n🔧 尝试自动修复...")
+ autoFix()
+ }
+
+ return nil
+}
+
+// printCheckResult prints a formatted check result.
+func printCheckResult(name, desc string, result checkResult) {
+ status := "✅"
+ if !result.OK {
+ if isRequired(name) {
+ status = "❌"
+ } else {
+ status = "⚠️ "
+ }
+ }
+
+ fmt.Printf("%s %-20s %s\n", status, name, result.Message)
+}
+
+// isRequired returns true if the check is required (not optional).
+func isRequired(name string) bool {
+ optional := map[string]bool{
+ "buf": true,
+ "api-linter": true,
+ }
+ return !optional[name]
+}
+
+// checkProtoc checks if protoc is installed.
+func checkProtoc() checkResult {
+ path, err := exec.LookPath("protoc")
+ if err != nil {
+ return checkResult{
+ OK: false,
+ Message: "未安装",
+ Help: getProtocInstallHelp(),
+ }
+ }
+
+ // Get version
+ out, err := exec.Command("protoc", "--version").Output()
+ if err != nil {
+ return checkResult{OK: true, Message: fmt.Sprintf("已安装 (%s)", path)}
+ }
+
+ version := strings.TrimSpace(string(out))
+ return checkResult{OK: true, Message: version}
+}
+
+// checkProtocGenGo checks if protoc-gen-go is installed.
+func checkProtocGenGo() checkResult {
+ path, err := exec.LookPath("protoc-gen-go")
+ if err != nil {
+ return checkResult{
+ OK: false,
+ Message: "未安装",
+ Help: "go install google.golang.org/protobuf/cmd/protoc-gen-go@latest",
+ }
+ }
+
+ return checkResult{OK: true, Message: fmt.Sprintf("已安装 (%s)", path)}
+}
+
+// checkProtocGenGoGrpc checks if protoc-gen-go-grpc is installed.
+func checkProtocGenGoGrpc() checkResult {
+ path, err := exec.LookPath("protoc-gen-go-grpc")
+ if err != nil {
+ return checkResult{
+ OK: false,
+ Message: "未安装",
+ Help: "go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest",
+ }
+ }
+
+ return checkResult{OK: true, Message: fmt.Sprintf("已安装 (%s)", path)}
+}
+
+// checkBuf checks if buf is installed.
+func checkBuf() checkResult {
+ path, err := exec.LookPath("buf")
+ if err != nil {
+ return checkResult{
+ OK: false,
+ Message: "未安装 (可选)",
+ Help: "go install github.com/bufbuild/buf/cmd/buf@latest",
+ }
+ }
+
+ out, err := exec.Command("buf", "--version").Output()
+ if err != nil {
+ return checkResult{OK: true, Message: fmt.Sprintf("已安装 (%s)", path)}
+ }
+
+ version := strings.TrimSpace(string(out))
+ return checkResult{OK: true, Message: fmt.Sprintf("v%s", version)}
+}
+
+// checkApiLinter checks if api-linter is installed.
+func checkApiLinter() checkResult {
+ path, err := exec.LookPath("api-linter")
+ if err != nil {
+ return checkResult{
+ OK: false,
+ Message: "未安装 (可选)",
+ Help: "go install github.com/googleapis/api-linter/cmd/api-linter@latest",
+ }
+ }
+
+ return checkResult{OK: true, Message: fmt.Sprintf("已安装 (%s)", path)}
+}
+
+// checkGo checks if Go is installed.
+func checkGo() checkResult {
+ path, err := exec.LookPath("go")
+ if err != nil {
+ return checkResult{
+ OK: false,
+ Message: "未安装",
+ Help: "请从 https://go.dev/dl/ 下载安装",
+ }
+ }
+
+ out, err := exec.Command("go", "version").Output()
+ if err != nil {
+ return checkResult{OK: true, Message: fmt.Sprintf("已安装 (%s)", path)}
+ }
+
+ // Extract version from "go version go1.21.0 darwin/amd64"
+ parts := strings.Split(string(out), " ")
+ if len(parts) >= 3 {
+ return checkResult{OK: true, Message: parts[2]}
+ }
+
+ return checkResult{OK: true, Message: "已安装"}
+}
+
+// checkConfig checks if project config file exists.
+func checkConfig() checkResult {
+ if _, err := os.Stat(protoCfg); os.IsNotExist(err) {
+ return checkResult{
+ OK: false,
+ Message: fmt.Sprintf("%s 不存在", protoCfg),
+ Help: "运行 'protobuild init' 初始化项目",
+ }
+ }
+
+ // Try to parse config
+ if err := parseConfig(); err != nil {
+ return checkResult{
+ OK: false,
+ Message: fmt.Sprintf("配置文件解析错误: %v", err),
+ Help: "检查 YAML 语法是否正确",
+ }
+ }
+
+ return checkResult{OK: true, Message: fmt.Sprintf("已配置 (%s)", protoCfg)}
+}
+
+// checkVendor checks if vendor directory exists and has dependencies.
+func checkVendor() checkResult {
+ if globalCfg.Vendor == "" {
+ return checkResult{
+ OK: false,
+ Message: "未配置 vendor 目录",
+ Help: "在配置文件中设置 vendor 字段",
+ }
+ }
+
+ if _, err := os.Stat(globalCfg.Vendor); os.IsNotExist(err) {
+ return checkResult{
+ OK: false,
+ Message: fmt.Sprintf("%s 目录不存在", globalCfg.Vendor),
+ Help: "运行 'protobuild vendor' 同步依赖",
+ }
+ }
+
+ // Count proto files in vendor
+ count := 0
+ filepath.Walk(globalCfg.Vendor, func(path string, info os.FileInfo, err error) error {
+ if err != nil {
+ return nil
+ }
+ if !info.IsDir() && strings.HasSuffix(path, ".proto") {
+ count++
+ }
+ return nil
+ })
+
+ if count == 0 {
+ return checkResult{
+ OK: false,
+ Message: fmt.Sprintf("%s 目录为空", globalCfg.Vendor),
+ Help: "运行 'protobuild vendor' 同步依赖",
+ }
+ }
+
+ return checkResult{OK: true, Message: fmt.Sprintf("%s (%d 个 proto 文件)", globalCfg.Vendor, count)}
+}
+
+// getProtocInstallHelp returns platform-specific install instructions for protoc.
+func getProtocInstallHelp() string {
+ switch runtime.GOOS {
+ case "darwin":
+ return "brew install protobuf"
+ case "linux":
+ return "apt install -y protobuf-compiler 或从 https://github.com/protocolbuffers/protobuf/releases 下载"
+ case "windows":
+ return "从 https://github.com/protocolbuffers/protobuf/releases 下载"
+ default:
+ return "从 https://github.com/protocolbuffers/protobuf/releases 下载"
+ }
+}
+
+// autoFix attempts to automatically fix common issues.
+func autoFix() {
+ // Check and install protoc-gen-go
+ if _, err := exec.LookPath("protoc-gen-go"); err != nil {
+ fmt.Println(" 安装 protoc-gen-go...")
+ cmd := exec.Command("go", "install", "google.golang.org/protobuf/cmd/protoc-gen-go@latest")
+ cmd.Stdout = os.Stdout
+ cmd.Stderr = os.Stderr
+ if err := cmd.Run(); err != nil {
+ fmt.Printf(" ❌ 安装失败: %v\n", err)
+ } else {
+ fmt.Println(" ✅ protoc-gen-go 安装成功")
+ }
+ }
+
+ // Check and install protoc-gen-go-grpc
+ if _, err := exec.LookPath("protoc-gen-go-grpc"); err != nil {
+ fmt.Println(" 安装 protoc-gen-go-grpc...")
+ cmd := exec.Command("go", "install", "google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest")
+ cmd.Stdout = os.Stdout
+ cmd.Stderr = os.Stderr
+ if err := cmd.Run(); err != nil {
+ fmt.Printf(" ❌ 安装失败: %v\n", err)
+ } else {
+ fmt.Println(" ✅ protoc-gen-go-grpc 安装成功")
+ }
+ }
+
+ // Run vendor if needed
+ if globalCfg.Vendor != "" {
+ if _, err := os.Stat(globalCfg.Vendor); os.IsNotExist(err) {
+ fmt.Println(" 同步依赖...")
+ // This would need to call the vendor command
+ fmt.Println(" 💡 请手动运行 'protobuild vendor'")
+ }
+ }
+}
diff --git a/cmd/protobuild/cmd_init.go b/cmd/protobuild/cmd_init.go
new file mode 100644
index 0000000..ea3a637
--- /dev/null
+++ b/cmd/protobuild/cmd_init.go
@@ -0,0 +1,288 @@
+package protobuild
+
+import (
+ "bufio"
+ "context"
+ "fmt"
+ "os"
+ "path/filepath"
+ "strings"
+
+ "github.com/pubgo/funk/recovery"
+ "github.com/pubgo/protobuild/internal/config"
+ "github.com/pubgo/protobuild/internal/typex"
+ "github.com/pubgo/redant"
+)
+
+// initTemplates defines available project templates.
+var initTemplates = map[string]*config.Config{
+ "basic": {
+ Vendor: ".proto",
+ Root: []string{"proto"},
+ Includes: []string{"proto"},
+ Depends: []*config.Depend{
+ {Name: "google/protobuf", Url: "https://github.com/protocolbuffers/protobuf", Path: "src/google/protobuf"},
+ },
+ Plugins: []*config.Plugin{
+ {Name: "go"},
+ {Name: "go-grpc"},
+ },
+ },
+ "grpc-gateway": {
+ Vendor: ".proto",
+ BasePlugin: &config.BasePluginCfg{
+ Out: "./pkg",
+ Paths: "import",
+ Module: "",
+ },
+ Root: []string{"proto"},
+ Includes: []string{"proto"},
+ Depends: []*config.Depend{
+ {Name: "google/protobuf", Url: "https://github.com/protocolbuffers/protobuf", Path: "src/google/protobuf"},
+ {Name: "google/api", Url: "https://github.com/googleapis/googleapis", Path: "google/api"},
+ {Name: "protoc-gen-openapiv2/options", Url: "https://github.com/grpc-ecosystem/grpc-gateway", Path: "protoc-gen-openapiv2/options"},
+ },
+ Plugins: []*config.Plugin{
+ {Name: "go"},
+ {Name: "go-grpc"},
+ {Name: "grpc-gateway", Opts: config.PluginOpts{"generate_unbound_methods=true"}},
+ {Name: "openapiv2", Out: "./docs/swagger"},
+ },
+ Installers: []string{
+ "go install google.golang.org/protobuf/cmd/protoc-gen-go@latest",
+ "go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest",
+ "go install github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-grpc-gateway@latest",
+ "go install github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2@latest",
+ },
+ },
+ "minimal": {
+ Vendor: ".proto",
+ Root: []string{"proto"},
+ Includes: []string{"proto"},
+ Plugins: []*config.Plugin{
+ {Name: "go"},
+ },
+ },
+}
+
+// newInitCommand creates the init command.
+func newInitCommand() *redant.Command {
+ var template string
+ var force bool
+
+ return &redant.Command{
+ Use: "init",
+ Short: "初始化 protobuf 项目配置",
+ Options: typex.Options{
+ redant.Option{
+ Flag: "template",
+ Shorthand: "t",
+ Description: "项目模板 (basic, grpc-gateway, minimal)",
+ Default: "",
+ Value: redant.StringOf(&template),
+ },
+ redant.Option{
+ Flag: "force",
+ Shorthand: "f",
+ Description: "覆盖已存在的配置文件",
+ Value: redant.BoolOf(&force),
+ },
+ },
+ Handler: func(ctx context.Context, inv *redant.Invocation) error {
+ defer recovery.Exit()
+ return runInit(template, force)
+ },
+ }
+}
+
+// runInit executes the init command logic.
+func runInit(template string, force bool) error {
+ // Check if config already exists
+ if _, err := os.Stat(protoCfg); err == nil && !force {
+ return fmt.Errorf("配置文件 %s 已存在,使用 --force 覆盖", protoCfg)
+ }
+
+ var cfg *config.Config
+
+ if template != "" {
+ // Use specified template
+ tmpl, ok := initTemplates[template]
+ if !ok {
+ fmt.Println("可用的模板:")
+ for name := range initTemplates {
+ fmt.Printf(" - %s\n", name)
+ }
+ return fmt.Errorf("未知的模板: %s", template)
+ }
+ cfg = tmpl
+ fmt.Printf("📦 使用模板: %s\n", template)
+ } else {
+ // Interactive mode
+ cfg = interactiveInit()
+ }
+
+ // Try to detect Go module
+ if cfg.BasePlugin != nil && cfg.BasePlugin.Module == "" {
+ if mod := detectGoModule(); mod != "" {
+ cfg.BasePlugin.Module = mod + "/pkg"
+ fmt.Printf("🔍 检测到 Go 模块: %s\n", mod)
+ }
+ }
+
+ // Create proto directory if not exists
+ for _, dir := range cfg.Root {
+ if err := os.MkdirAll(dir, 0755); err != nil {
+ return fmt.Errorf("创建目录 %s 失败: %w", dir, err)
+ }
+ fmt.Printf("📁 创建目录: %s\n", dir)
+ }
+
+ // Save config
+ if err := config.Save(protoCfg, cfg); err != nil {
+ return fmt.Errorf("保存配置失败: %w", err)
+ }
+
+ fmt.Printf("✅ 配置文件已创建: %s\n", protoCfg)
+ fmt.Println("\n下一步:")
+ fmt.Println(" 1. 运行 'protobuild vendor' 同步依赖")
+ fmt.Println(" 2. 在 proto/ 目录下创建 .proto 文件")
+ fmt.Println(" 3. 运行 'protobuild gen' 生成代码")
+
+ return nil
+}
+
+// interactiveInit runs interactive configuration setup.
+func interactiveInit() *config.Config {
+ reader := bufio.NewReader(os.Stdin)
+
+ fmt.Println("🚀 Protobuild 项目初始化")
+ fmt.Println("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━")
+
+ // Proto source directory
+ fmt.Print("Proto 源目录 [proto]: ")
+ protoDir, _ := reader.ReadString('\n')
+ protoDir = strings.TrimSpace(protoDir)
+ if protoDir == "" {
+ protoDir = "proto"
+ }
+
+ // Output directory
+ fmt.Print("代码输出目录 [./pkg]: ")
+ outDir, _ := reader.ReadString('\n')
+ outDir = strings.TrimSpace(outDir)
+ if outDir == "" {
+ outDir = "./pkg"
+ }
+
+ // Path mode
+ fmt.Print("路径模式 (source_relative/import) [source_relative]: ")
+ pathMode, _ := reader.ReadString('\n')
+ pathMode = strings.TrimSpace(pathMode)
+ if pathMode == "" {
+ pathMode = "source_relative"
+ }
+
+ // gRPC support
+ fmt.Print("是否需要 gRPC 支持? (y/n) [y]: ")
+ grpcInput, _ := reader.ReadString('\n')
+ grpcInput = strings.TrimSpace(strings.ToLower(grpcInput))
+ needGrpc := grpcInput == "" || grpcInput == "y" || grpcInput == "yes"
+
+ // Build config
+ cfg := &config.Config{
+ Vendor: ".proto",
+ BasePlugin: &config.BasePluginCfg{
+ Out: outDir,
+ Paths: pathMode,
+ },
+ Root: []string{protoDir},
+ Includes: []string{protoDir},
+ Depends: []*config.Depend{
+ {Name: "google/protobuf", Url: "https://github.com/protocolbuffers/protobuf", Path: "src/google/protobuf"},
+ },
+ Plugins: []*config.Plugin{
+ {Name: "go"},
+ },
+ }
+
+ if needGrpc {
+ cfg.Plugins = append(cfg.Plugins, &config.Plugin{Name: "go-grpc"})
+ }
+
+ return cfg
+}
+
+// detectGoModule tries to detect the Go module name from go.mod.
+func detectGoModule() string {
+ data, err := os.ReadFile("go.mod")
+ if err != nil {
+ return ""
+ }
+
+ lines := strings.Split(string(data), "\n")
+ for _, line := range lines {
+ line = strings.TrimSpace(line)
+ if strings.HasPrefix(line, "module ") {
+ return strings.TrimSpace(strings.TrimPrefix(line, "module "))
+ }
+ }
+
+ return ""
+}
+
+// createExampleProto creates an example proto file if the directory is empty.
+func createExampleProto(protoDir string) error {
+ // Check if directory is empty
+ entries, err := os.ReadDir(protoDir)
+ if err != nil {
+ return err
+ }
+
+ for _, e := range entries {
+ if strings.HasSuffix(e.Name(), ".proto") {
+ return nil // Already has proto files
+ }
+ }
+
+ // Create example proto
+ exampleDir := filepath.Join(protoDir, "example", "v1")
+ if err := os.MkdirAll(exampleDir, 0755); err != nil {
+ return err
+ }
+
+ module := detectGoModule()
+ if module == "" {
+ module = "github.com/yourorg/yourproject"
+ }
+
+ example := fmt.Sprintf(`syntax = "proto3";
+
+package example.v1;
+
+option go_package = "%s/pkg/example/v1;examplev1";
+
+// HelloRequest is the request message for Hello.
+message HelloRequest {
+ string name = 1;
+}
+
+// HelloResponse is the response message for Hello.
+message HelloResponse {
+ string message = 1;
+}
+
+// GreeterService is a simple greeting service.
+service GreeterService {
+ // SayHello returns a greeting.
+ rpc SayHello(HelloRequest) returns (HelloResponse);
+}
+`, module)
+
+ exampleFile := filepath.Join(exampleDir, "greeter.proto")
+ if err := os.WriteFile(exampleFile, []byte(example), 0644); err != nil {
+ return err
+ }
+
+ fmt.Printf("📝 创建示例文件: %s\n", exampleFile)
+ return nil
+}
diff --git a/cmd/protobuild/commands.go b/cmd/protobuild/commands.go
index be223e5..03d4330 100644
--- a/cmd/protobuild/commands.go
+++ b/cmd/protobuild/commands.go
@@ -86,7 +86,7 @@ func newVendorCommand(force, update *bool) *redant.Command {
return nil
}
- if !result.Changed && !globalCfg.changed && !*force {
+ if !result.Changed && !globalCfg.Changed && !*force {
fmt.Println("\n✨ No changes detected")
return nil
}
diff --git a/cmd/protobuild/config.go b/cmd/protobuild/config.go
index 8a21c1e..25c2d54 100644
--- a/cmd/protobuild/config.go
+++ b/cmd/protobuild/config.go
@@ -1,64 +1,14 @@
package protobuild
import (
- "github.com/pubgo/protobuild/cmd/linters"
+ "github.com/pubgo/protobuild/internal/config"
)
-type Config struct {
- Checksum string `yaml:"checksum,omitempty" hash:"-"`
- Vendor string `yaml:"vendor,omitempty"`
- BasePlugin *basePluginCfg `yaml:"base,omitempty" hash:"-"`
-
- // Root path, default is proto path
- // source path
- Root []string `yaml:"root,omitempty" hash:"-"`
-
- // Includes protoc include path, default is proto path and .proto path
- Includes []string `yaml:"includes,omitempty" hash:"-"`
- Excludes []string `yaml:"excludes,omitempty" hash:"-"`
- Depends []*depend `yaml:"deps,omitempty"`
- Plugins []*plugin `yaml:"plugins,omitempty" hash:"-"`
- changed bool
- Installers []string `yaml:"installers,omitempty" hash:"-"`
- Linter linters.LinterConfig `yaml:"linter,omitempty" hash:"-"`
-}
-
-type basePluginCfg struct {
- Out string `yaml:"out,omitempty"`
- Paths string `yaml:"paths,omitempty"`
- Module string `yaml:"module,omitempty"`
-}
-
-type plugin struct {
- // Name protoc plugin name
- Name string `yaml:"name,omitempty"`
-
- // Path protoc plugin path
- Path string `yaml:"path,omitempty"`
-
- Out string `yaml:"out,omitempty"`
- Shell string `yaml:"shell,omitempty"`
- Docker string `yaml:"docker,omitempty"`
- Remote string `yaml:"remote,omitempty"`
-
- // SkipBase skip base config
- SkipBase bool `yaml:"skip_base,omitempty"`
-
- // SkipRun skip run plugin
- SkipRun bool `yaml:"skip_run,omitempty"`
-
- // ExcludeOpts exclude plugin opts
- ExcludeOpts pluginOpts `yaml:"exclude_opts,omitempty"`
- Opt pluginOpts `yaml:"opt,omitempty"`
- Opts pluginOpts `yaml:"opts,omitempty"`
-}
-
-type depend struct {
- Name string `yaml:"name,omitempty"`
- Source string `yaml:"source,omitempty"` // gomod(default), git, http, s3, gcs, local
- Url string `yaml:"url,omitempty"`
- Path string `yaml:"path,omitempty"`
- Version *string `yaml:"version,omitempty"` // for gomod
- Ref string `yaml:"ref,omitempty"` // for git: tag/branch/commit
- Optional *bool `yaml:"optional,omitempty"`
-}
+// Type aliases for backward compatibility
+type (
+ Config = config.Config
+ basePluginCfg = config.BasePluginCfg
+ plugin = config.Plugin
+ depend = config.Depend
+ pluginOpts = config.PluginOpts
+)
diff --git a/cmd/protobuild/protoc_builder.go b/cmd/protobuild/protoc_builder.go
index 9876198..061022a 100644
--- a/cmd/protobuild/protoc_builder.go
+++ b/cmd/protobuild/protoc_builder.go
@@ -135,14 +135,14 @@ func (c *ProtocCommand) buildPluginArgs(plg *plugin) string {
args.WriteString(fmt.Sprintf(" --plugin=protoc-gen-%s=%s", name, wrapperPath))
}
- // Handle retag plugin specially
+ // Handle retag plugin specially - run after main compilation to modify generated files
if name == reTagPluginName {
- opts = append(opts, "__out="+out)
args.WriteString(fmt.Sprintf(" --%s_out=%s", name, out))
- args.WriteString(fmt.Sprintf(" --%s_opt=%s", name, strings.Join(opts, ",")))
- if plg.Path != "" {
- plgPath := assert.Must1(exec.LookPath(plg.Path))
- args.WriteString(fmt.Sprintf(" --plugin=protoc-gen-%s=%s", name, plgPath))
+ if len(opts) > 0 {
+ filteredOpts := c.filterExcludedOpts(opts, plg.ExcludeOpts)
+ if len(filteredOpts) > 0 {
+ args.WriteString(fmt.Sprintf(" --%s_opt=%s", name, strings.Join(filteredOpts, ",")))
+ }
}
return args.String()
}
diff --git a/cmd/protobuild/util.go b/cmd/protobuild/util.go
index 1a75076..19affa0 100644
--- a/cmd/protobuild/util.go
+++ b/cmd/protobuild/util.go
@@ -8,14 +8,38 @@ import (
"github.com/a8m/envsubst"
"github.com/cnf/structhash"
+ "github.com/googleapis/api-linter/v2/lint"
"github.com/huandu/go-clone"
"github.com/pubgo/funk/assert"
"github.com/pubgo/funk/errors"
"github.com/pubgo/funk/pathutil"
"github.com/pubgo/funk/strutil"
+ "github.com/pubgo/protobuild/cmd/linters"
+ "github.com/pubgo/protobuild/internal/config"
"gopkg.in/yaml.v3"
)
+// toLinterConfig converts the shared config Linter to linters.LinterConfig.
+func toLinterConfig(l *config.Linter) linters.LinterConfig {
+ if l == nil {
+ return linters.LinterConfig{}
+ }
+
+ cfg := linters.LinterConfig{
+ FormatType: l.FormatType,
+ IgnoreCommentDisablesFlag: l.IgnoreCommentDisablesFlag,
+ }
+
+ if l.Rules != nil {
+ cfg.Rules = lint.Config{
+ EnabledRules: l.Rules.EnabledRules,
+ DisabledRules: l.Rules.DisabledRules,
+ }
+ }
+
+ return cfg
+}
+
func mergePluginConfig(base *Config, pluginConfigs ...*Config) *Config {
base = clone.Clone(base).(*Config)
for _, cfg := range pluginConfigs {
@@ -83,7 +107,7 @@ func parseConfig() error {
checksum := fmt.Sprintf("%x", structhash.Sha1(globalCfg, 1))
if globalCfg.Checksum != checksum {
globalCfg.Checksum = checksum
- globalCfg.changed = true
+ globalCfg.Changed = true
}
oldChecksum, err := getChecksumData(globalCfg.Vendor)
@@ -91,7 +115,7 @@ func parseConfig() error {
slog.Warn("failed to get checksum data", slog.Any("err", err.Error()))
}
if oldChecksum != checksum {
- globalCfg.changed = true
+ globalCfg.Changed = true
}
return nil
diff --git a/cmd/protobuild/yaml_types.go b/cmd/protobuild/yaml_types.go
index 2e15c4c..4bca88e 100644
--- a/cmd/protobuild/yaml_types.go
+++ b/cmd/protobuild/yaml_types.go
@@ -6,33 +6,8 @@ import (
yaml "gopkg.in/yaml.v3"
)
-var _ yaml.Unmarshaler = (*pluginOpts)(nil)
-
-type pluginOpts []string
-
-func (p *pluginOpts) UnmarshalYAML(value *yaml.Node) error {
- if value.IsZero() {
- return nil
- }
-
- switch value.Kind {
- case yaml.ScalarNode:
- if value.Value != "" {
- *p = []string{value.Value}
- return nil
- }
- return nil
- case yaml.SequenceNode:
- var data []string
- if err := value.Decode(&data); err != nil {
- return err
- }
- *p = data
- return nil
- default:
- return errors.Format("yaml kind type error, kind=%v data=%s", value.Kind, value.Value)
- }
-}
+// Note: pluginOpts is now defined in internal/config/yaml_types.go
+// and aliased in config.go
type YamlListType[T any] []T
diff --git a/cmd/webcmd/cmd.go b/cmd/webcmd/cmd.go
new file mode 100644
index 0000000..3d6b8bc
--- /dev/null
+++ b/cmd/webcmd/cmd.go
@@ -0,0 +1,69 @@
+// Package webcmd provides CLI command for the web UI.
+package webcmd
+
+import (
+ "context"
+ "os"
+ "os/signal"
+ "strconv"
+ "syscall"
+
+ "github.com/pubgo/protobuild/internal/typex"
+ "github.com/pubgo/redant"
+)
+
+// New creates the web command.
+func New(configPath *string) *redant.Command {
+ var portStr string
+
+ return &redant.Command{
+ Use: "web",
+ Short: "启动 Web 配置管理界面",
+ Long: `启动一个本地 Web 服务器,提供可视化的配置管理界面。
+
+通过浏览器可以:
+ - 查看和编辑项目配置
+ - 管理 Proto 依赖
+ - 配置 Protoc 插件
+ - 执行构建、检查、格式化等操作
+ - 实时预览 YAML 配置
+
+示例:
+ protobuild web
+ protobuild web --port 9090`,
+ Options: typex.Options{
+ redant.Option{
+ Flag: "port",
+ Shorthand: "p",
+ Description: "Web 服务器端口",
+ Default: "8080",
+ Value: redant.StringOf(&portStr),
+ },
+ },
+ Handler: func(ctx context.Context, inv *redant.Invocation) error {
+ port, _ := strconv.Atoi(portStr)
+ if port == 0 {
+ port = 8080
+ }
+
+ server, err := NewServer(*configPath)
+ if err != nil {
+ return err
+ }
+
+ // Handle signals for graceful shutdown
+ ctx, cancel := context.WithCancel(ctx)
+ defer cancel()
+
+ sigCh := make(chan os.Signal, 1)
+ signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)
+
+ go func() {
+ <-sigCh
+ cancel()
+ }()
+
+ return server.Start(ctx, port)
+ },
+ }
+}
diff --git a/cmd/webcmd/server.go b/cmd/webcmd/server.go
new file mode 100644
index 0000000..44acd7b
--- /dev/null
+++ b/cmd/webcmd/server.go
@@ -0,0 +1,554 @@
+// Package webcmd provides a web-based configuration UI for protobuild.
+package webcmd
+
+import (
+ "bufio"
+ "context"
+ "embed"
+ "encoding/json"
+ "fmt"
+ "html/template"
+ "io/fs"
+ "log/slog"
+ "net"
+ "net/http"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "runtime"
+ "strings"
+ "sync"
+ "time"
+
+ "github.com/pubgo/protobuild/internal/config"
+)
+
+//go:embed templates/*
+var templateFS embed.FS
+
+// CommandResult represents the result of a command execution.
+type CommandResult struct {
+ Success bool `json:"success"`
+ Output string `json:"output"`
+ Error string `json:"error,omitempty"`
+}
+
+// Server represents the web server.
+type Server struct {
+ configPath string
+ config *config.Config
+ mu sync.RWMutex
+ server *http.Server
+ templates *template.Template
+}
+
+// NewServer creates a new web server.
+func NewServer(configPath string) (*Server, error) {
+ s := &Server{
+ configPath: configPath,
+ }
+
+ // Load templates
+ tmpl, err := template.ParseFS(templateFS, "templates/*.html")
+ if err != nil {
+ return nil, fmt.Errorf("failed to parse templates: %w", err)
+ }
+ s.templates = tmpl
+
+ // Load initial config
+ if err := s.loadConfig(); err != nil {
+ // Create default config if not exists
+ s.config = config.Default()
+ }
+
+ return s, nil
+}
+
+// loadConfig loads the configuration from file.
+func (s *Server) loadConfig() error {
+ s.mu.Lock()
+ defer s.mu.Unlock()
+
+ cfg, err := config.Load(s.configPath)
+ if err != nil {
+ return err
+ }
+
+ s.config = cfg
+ return nil
+}
+
+// saveConfig saves the configuration to file.
+func (s *Server) saveConfig() error {
+ s.mu.RLock()
+ cfg := s.config
+ s.mu.RUnlock()
+
+ return config.Save(s.configPath, cfg)
+}
+
+// Start starts the web server.
+func (s *Server) Start(ctx context.Context, port int) error {
+ mux := http.NewServeMux()
+
+ // Static files
+ mux.HandleFunc("/", s.handleIndex)
+
+ // API endpoints
+ mux.HandleFunc("/api/config", s.handleConfig)
+ mux.HandleFunc("/api/config/save", s.handleSaveConfig)
+ mux.HandleFunc("/api/command/", s.handleCommand)
+ mux.HandleFunc("/api/command-stream/", s.handleCommandStream)
+ mux.HandleFunc("/api/proto-files", s.handleProtoFiles)
+ mux.HandleFunc("/api/proto-content", s.handleProtoContent)
+ mux.HandleFunc("/api/deps/status", s.handleDepsStatus)
+ mux.HandleFunc("/api/project/stats", s.handleProjectStats)
+
+ addr := fmt.Sprintf(":%d", port)
+ s.server = &http.Server{
+ Addr: addr,
+ Handler: mux,
+ }
+
+ // Find available port if default is in use
+ listener, err := net.Listen("tcp", addr)
+ if err != nil {
+ // Try to find an available port
+ listener, err = net.Listen("tcp", ":0")
+ if err != nil {
+ return fmt.Errorf("failed to find available port: %w", err)
+ }
+ }
+
+ actualPort := listener.Addr().(*net.TCPAddr).Port
+ url := fmt.Sprintf("http://localhost:%d", actualPort)
+
+ slog.Info("Starting web server", "url", url)
+ fmt.Printf("\n🌐 Web UI available at: %s\n\n", url)
+
+ // Open browser
+ go func() {
+ time.Sleep(500 * time.Millisecond)
+ openBrowser(url)
+ }()
+
+ // Handle graceful shutdown
+ go func() {
+ <-ctx.Done()
+ shutdownCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
+ defer cancel()
+ s.server.Shutdown(shutdownCtx)
+ }()
+
+ return s.server.Serve(listener)
+}
+
+// handleIndex serves the main page.
+func (s *Server) handleIndex(w http.ResponseWriter, r *http.Request) {
+ if r.URL.Path != "/" {
+ http.NotFound(w, r)
+ return
+ }
+
+ s.mu.RLock()
+ cfg := s.config
+ s.mu.RUnlock()
+
+ data := map[string]interface{}{
+ "Config": cfg,
+ "ConfigPath": s.configPath,
+ }
+
+ w.Header().Set("Content-Type", "text/html; charset=utf-8")
+ if err := s.templates.ExecuteTemplate(w, "index.html", data); err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ }
+}
+
+// handleConfig returns the current configuration.
+func (s *Server) handleConfig(w http.ResponseWriter, r *http.Request) {
+ if r.Method == http.MethodGet {
+ // Reload config from file
+ s.loadConfig()
+
+ s.mu.RLock()
+ cfg := s.config
+ s.mu.RUnlock()
+
+ w.Header().Set("Content-Type", "application/json")
+ json.NewEncoder(w).Encode(cfg)
+ return
+ }
+
+ http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
+}
+
+// handleSaveConfig saves the configuration.
+func (s *Server) handleSaveConfig(w http.ResponseWriter, r *http.Request) {
+ if r.Method != http.MethodPost {
+ http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
+ return
+ }
+
+ var cfg config.Config
+ if err := json.NewDecoder(r.Body).Decode(&cfg); err != nil {
+ http.Error(w, err.Error(), http.StatusBadRequest)
+ return
+ }
+
+ s.mu.Lock()
+ s.config = &cfg
+ s.mu.Unlock()
+
+ if err := s.saveConfig(); err != nil {
+ w.Header().Set("Content-Type", "application/json")
+ json.NewEncoder(w).Encode(CommandResult{
+ Success: false,
+ Error: err.Error(),
+ })
+ return
+ }
+
+ w.Header().Set("Content-Type", "application/json")
+ json.NewEncoder(w).Encode(CommandResult{
+ Success: true,
+ Output: "Configuration saved successfully",
+ })
+}
+
+// handleCommand executes protobuild commands.
+func (s *Server) handleCommand(w http.ResponseWriter, r *http.Request) {
+ if r.Method != http.MethodPost {
+ http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
+ return
+ }
+
+ // Extract command from URL path
+ cmdName := strings.TrimPrefix(r.URL.Path, "/api/command/")
+ if cmdName == "" {
+ http.Error(w, "Command name required", http.StatusBadRequest)
+ return
+ }
+
+ // Build command arguments
+ args := []string{"-c", s.configPath, cmdName}
+
+ // Parse additional flags from request body
+ var flags map[string]interface{}
+ if err := json.NewDecoder(r.Body).Decode(&flags); err == nil {
+ for key, val := range flags {
+ switch v := val.(type) {
+ case bool:
+ if v {
+ args = append(args, "--"+key)
+ }
+ case string:
+ if v != "" {
+ args = append(args, "--"+key, v)
+ }
+ }
+ }
+ }
+
+ // Get executable path
+ executable, err := os.Executable()
+ if err != nil {
+ executable = "protobuild"
+ }
+
+ // Execute command
+ ctx, cancel := context.WithTimeout(r.Context(), 60*time.Second)
+ defer cancel()
+
+ cmd := exec.CommandContext(ctx, executable, args...)
+ cmd.Dir = filepath.Dir(s.configPath)
+
+ output, err := cmd.CombinedOutput()
+
+ result := CommandResult{
+ Success: err == nil,
+ Output: string(output),
+ }
+ if err != nil {
+ result.Error = err.Error()
+ }
+
+ w.Header().Set("Content-Type", "application/json")
+ json.NewEncoder(w).Encode(result)
+}
+
+// handleProtoFiles returns a list of proto files in the project.
+func (s *Server) handleProtoFiles(w http.ResponseWriter, r *http.Request) {
+ s.mu.RLock()
+ cfg := s.config
+ s.mu.RUnlock()
+
+ var files []string
+ baseDir := filepath.Dir(s.configPath)
+
+ for _, root := range cfg.Root {
+ rootPath := filepath.Join(baseDir, root)
+ filepath.Walk(rootPath, func(path string, info fs.FileInfo, err error) error {
+ if err != nil {
+ return nil
+ }
+ if !info.IsDir() && strings.HasSuffix(path, ".proto") {
+ relPath, _ := filepath.Rel(baseDir, path)
+ files = append(files, relPath)
+ }
+ return nil
+ })
+ }
+
+ w.Header().Set("Content-Type", "application/json")
+ json.NewEncoder(w).Encode(files)
+}
+
+// handleDepsStatus returns the status of dependencies.
+func (s *Server) handleDepsStatus(w http.ResponseWriter, r *http.Request) {
+ // Get executable path
+ executable, err := os.Executable()
+ if err != nil {
+ executable = "protobuild"
+ }
+
+ cmd := exec.Command(executable, "-c", s.configPath, "deps")
+ cmd.Dir = filepath.Dir(s.configPath)
+ output, _ := cmd.CombinedOutput()
+
+ w.Header().Set("Content-Type", "application/json")
+ json.NewEncoder(w).Encode(map[string]string{
+ "output": string(output),
+ })
+}
+
+// openBrowser opens the URL in the default browser.
+func openBrowser(url string) error {
+ var cmd string
+ var args []string
+
+ switch runtime.GOOS {
+ case "darwin":
+ cmd = "open"
+ args = []string{url}
+ case "linux":
+ cmd = "xdg-open"
+ args = []string{url}
+ case "windows":
+ cmd = "rundll32"
+ args = []string{"url.dll,FileProtocolHandler", url}
+ default:
+ return fmt.Errorf("unsupported platform")
+ }
+
+ return exec.Command(cmd, args...).Start()
+}
+
+// handleCommandStream executes a command and streams output via SSE.
+func (s *Server) handleCommandStream(w http.ResponseWriter, r *http.Request) {
+ if r.Method != http.MethodPost {
+ http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
+ return
+ }
+
+ // Extract command from URL path
+ cmdName := strings.TrimPrefix(r.URL.Path, "/api/command-stream/")
+ if cmdName == "" {
+ http.Error(w, "Command name required", http.StatusBadRequest)
+ return
+ }
+
+ // Set SSE headers
+ w.Header().Set("Content-Type", "text/event-stream")
+ w.Header().Set("Cache-Control", "no-cache")
+ w.Header().Set("Connection", "keep-alive")
+ w.Header().Set("Access-Control-Allow-Origin", "*")
+
+ flusher, ok := w.(http.Flusher)
+ if !ok {
+ http.Error(w, "Streaming not supported", http.StatusInternalServerError)
+ return
+ }
+
+ // Build command arguments
+ args := []string{"-c", s.configPath, cmdName}
+
+ // Get executable path
+ executable, err := os.Executable()
+ if err != nil {
+ executable = "protobuild"
+ }
+
+ // Execute command with streaming output
+ ctx, cancel := context.WithTimeout(r.Context(), 120*time.Second)
+ defer cancel()
+
+ cmd := exec.CommandContext(ctx, executable, args...)
+ cmd.Dir = filepath.Dir(s.configPath)
+
+ // Create pipes for stdout and stderr
+ stdout, _ := cmd.StdoutPipe()
+ stderr, _ := cmd.StderrPipe()
+
+ if err := cmd.Start(); err != nil {
+ fmt.Fprintf(w, "data: {\"type\":\"error\",\"data\":\"%s\"}\n\n", err.Error())
+ flusher.Flush()
+ return
+ }
+
+ // Stream output
+ go func() {
+ scanner := bufio.NewScanner(stdout)
+ for scanner.Scan() {
+ line := scanner.Text()
+ fmt.Fprintf(w, "data: {\"type\":\"stdout\",\"data\":\"%s\"}\n\n", escapeJSON(line))
+ flusher.Flush()
+ }
+ }()
+
+ go func() {
+ scanner := bufio.NewScanner(stderr)
+ for scanner.Scan() {
+ line := scanner.Text()
+ fmt.Fprintf(w, "data: {\"type\":\"stderr\",\"data\":\"%s\"}\n\n", escapeJSON(line))
+ flusher.Flush()
+ }
+ }()
+
+ err = cmd.Wait()
+ if err != nil {
+ fmt.Fprintf(w, "data: {\"type\":\"error\",\"data\":\"%s\"}\n\n", err.Error())
+ } else {
+ fmt.Fprintf(w, "data: {\"type\":\"done\",\"data\":\"Command completed successfully\"}\n\n")
+ }
+ flusher.Flush()
+}
+
+// escapeJSON escapes special characters for JSON string.
+func escapeJSON(s string) string {
+ s = strings.ReplaceAll(s, "\\", "\\\\")
+ s = strings.ReplaceAll(s, "\"", "\\\"")
+ s = strings.ReplaceAll(s, "\n", "\\n")
+ s = strings.ReplaceAll(s, "\r", "\\r")
+ s = strings.ReplaceAll(s, "\t", "\\t")
+ return s
+}
+
+// handleProtoContent returns the content of a specific proto file.
+func (s *Server) handleProtoContent(w http.ResponseWriter, r *http.Request) {
+ filePath := r.URL.Query().Get("file")
+ if filePath == "" {
+ http.Error(w, "File path required", http.StatusBadRequest)
+ return
+ }
+
+ baseDir := filepath.Dir(s.configPath)
+ fullPath := filepath.Join(baseDir, filePath)
+
+ // Security check: ensure the path is within the project
+ absBase, _ := filepath.Abs(baseDir)
+ absPath, _ := filepath.Abs(fullPath)
+ if !strings.HasPrefix(absPath, absBase) {
+ http.Error(w, "Invalid file path", http.StatusForbidden)
+ return
+ }
+
+ // Check file extension
+ if !strings.HasSuffix(fullPath, ".proto") {
+ http.Error(w, "Only .proto files allowed", http.StatusForbidden)
+ return
+ }
+
+ content, err := os.ReadFile(fullPath)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusNotFound)
+ return
+ }
+
+ // Get file info
+ info, _ := os.Stat(fullPath)
+
+ w.Header().Set("Content-Type", "application/json")
+ json.NewEncoder(w).Encode(map[string]interface{}{
+ "path": filePath,
+ "content": string(content),
+ "size": info.Size(),
+ "modified": info.ModTime().Format(time.RFC3339),
+ })
+}
+
+// ProjectStats represents project statistics.
+type ProjectStats struct {
+ ProtoFiles int `json:"proto_files"`
+ TotalLines int `json:"total_lines"`
+ MessageCount int `json:"message_count"`
+ ServiceCount int `json:"service_count"`
+ DependencyCount int `json:"dependency_count"`
+ PluginCount int `json:"plugin_count"`
+ ProtoRoots []string `json:"proto_roots"`
+ VendorDir string `json:"vendor_dir"`
+ VendorFiles int `json:"vendor_files"`
+}
+
+// handleProjectStats returns project statistics.
+func (s *Server) handleProjectStats(w http.ResponseWriter, r *http.Request) {
+ s.mu.RLock()
+ cfg := s.config
+ s.mu.RUnlock()
+
+ stats := ProjectStats{
+ ProtoRoots: cfg.Root,
+ VendorDir: cfg.Vendor,
+ DependencyCount: len(cfg.Depends),
+ PluginCount: len(cfg.Plugins),
+ }
+
+ baseDir := filepath.Dir(s.configPath)
+
+ // Count proto files in root directories
+ for _, root := range cfg.Root {
+ rootPath := filepath.Join(baseDir, root)
+ filepath.Walk(rootPath, func(path string, info fs.FileInfo, err error) error {
+ if err != nil {
+ return nil
+ }
+ if !info.IsDir() && strings.HasSuffix(path, ".proto") {
+ stats.ProtoFiles++
+
+ // Count lines, messages, and services
+ content, err := os.ReadFile(path)
+ if err == nil {
+ lines := strings.Split(string(content), "\n")
+ stats.TotalLines += len(lines)
+
+ for _, line := range lines {
+ trimmed := strings.TrimSpace(line)
+ if strings.HasPrefix(trimmed, "message ") {
+ stats.MessageCount++
+ } else if strings.HasPrefix(trimmed, "service ") {
+ stats.ServiceCount++
+ }
+ }
+ }
+ }
+ return nil
+ })
+ }
+
+ // Count vendor files
+ if cfg.Vendor != "" {
+ vendorPath := filepath.Join(baseDir, cfg.Vendor)
+ filepath.Walk(vendorPath, func(path string, info fs.FileInfo, err error) error {
+ if err != nil {
+ return nil
+ }
+ if !info.IsDir() && strings.HasSuffix(path, ".proto") {
+ stats.VendorFiles++
+ }
+ return nil
+ })
+ }
+
+ w.Header().Set("Content-Type", "application/json")
+ json.NewEncoder(w).Encode(stats)
+}
diff --git a/cmd/webcmd/templates/index.html b/cmd/webcmd/templates/index.html
new file mode 100644
index 0000000..c4933ce
--- /dev/null
+++ b/cmd/webcmd/templates/index.html
@@ -0,0 +1,1605 @@
+
+
+
+
+
+ Protobuild - 配置管理
+
+
+
+
+
+
+
+
+
+
+
+
🔧 Protobuild
+
Protocol Buffers 构建与管理工具
+
+
+
配置文件: {{.ConfigPath}}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
⚡ 快速操作
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 运行中...
+
+ 命令输出
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
依赖存放的目录
+
+
+
+
+
+
+
插件生成代码的默认目录
+
+
+
+
+
+
+
protoc 路径模式
+
+
+
+
+
+
+
Go 模块路径
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Proto 依赖
+
+
+
+
+
+
+
+
+
+ 暂无依赖配置,点击"添加依赖"开始
+
+
+
+
+
+
+
+
Protoc 插件
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 暂无插件配置,点击上方按钮添加
+
+
+
+
+
+
插件安装器
+
通过 go install 安装的插件列表
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Linter 配置
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
💡 常用规则
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ messages
+ services
+ imports
+ lines
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
📚 配置示例参考
+
以下是常用的配置示例,点击「应用此配置」可直接使用。
+
+
+
+
+
+
+
🚀 基础 Go 项目
+
适合简单的 Go + gRPC 项目
+
+
+
+
vendor: .proto
+base:
+ out: ./gen
+ paths: source_relative
+root:
+ - proto
+includes:
+ - proto
+deps:
+ - name: google/protobuf
+ url: https://github.com/protocolbuffers/protobuf
+ path: src/google/protobuf
+plugins:
+ - name: go
+ - name: go-grpc
+
+
+
+
+
+
+
⚡ 完整项目配置
+
包含 gRPC-Gateway、Validate、OpenAPI 等
+
+
+
+
vendor: .proto
+base:
+ out: ./pkg
+ paths: import
+ module: github.com/yourorg/yourproject/pkg
+root:
+ - proto
+ - api
+includes:
+ - proto
+ - third_party
+deps:
+ - name: google/protobuf
+ url: https://github.com/protocolbuffers/protobuf
+ path: src/google/protobuf
+ - name: google/api
+ url: https://github.com/googleapis/googleapis
+ path: google/api
+ - name: protoc-gen-openapiv2/options
+ url: https://github.com/grpc-ecosystem/grpc-gateway
+ path: protoc-gen-openapiv2/options
+ - name: validate
+ url: https://github.com/bufbuild/protovalidate
+ path: proto/protovalidate/buf/validate
+plugins:
+ - name: go
+ - name: go-grpc
+ - name: grpc-gateway
+ opt:
+ - generate_unbound_methods=true
+ - name: openapiv2
+ out: ./docs/swagger
+ - name: validate-go
+linter:
+ format_type: github_actions
+ rules:
+ enabled_rules:
+ - core
+ - naming
+ disabled_rules: []
+
+
+
+
+
+
vendor: .proto
+base:
+ out: ./internal/pb
+ paths: import
+ module: github.com/yourorg/service/internal/pb
+root:
+ - api/proto
+includes:
+ - api/proto
+ - vendor/proto
+excludes:
+ - vendor
+ - third_party
+deps:
+ - name: google/protobuf
+ url: https://github.com/protocolbuffers/protobuf
+ path: src/google/protobuf
+ - name: google/api
+ url: https://github.com/googleapis/googleapis
+ path: google/api
+plugins:
+ - name: go
+ opt:
+ - paths=source_relative
+ - name: go-grpc
+ opt:
+ - paths=source_relative
+ - require_unimplemented_servers=false
+ - name: connect-go
+ out: ./internal/connect
+linter:
+ format_type: yaml
+ rules:
+ enabled_rules:
+ - core::0131::http-method
+ - core::0131::http-body
+ disabled_rules:
+ - all
+
+
+
+
+
+
+
🏷️ 自定义结构体标签
+
使用 retag 添加 JSON/XML/Validate 标签
+
+
+
+
vendor: .proto
+base:
+ out: ./pkg
+ paths: import
+ module: github.com/yourorg/yourproject/pkg
+root:
+ - proto
+includes:
+ - proto
+deps:
+ - name: google/protobuf
+ url: https://github.com/protocolbuffers/protobuf
+ path: src/google/protobuf
+ - name: retag
+ url: https://github.com/pubgo/protoc-gen-retag
+ path: proto/retag
+plugins:
+ - name: go
+ - name: retag
+# Proto 文件中使用示例:
+# message User {
+# string id = 1 [(retag.tags) = {name: "json", value: "user_id"}];
+# string email = 2 [
+# (retag.tags) = {name: "json", value: "email"},
+# (retag.tags) = {name: "xml", value: "email,attr"},
+# (retag.tags) = {name: "validate", value: "required,email"}
+# ];
+# }
+
+
+
+
+
+
+
📦 Buf 兼容配置
+
与 Buf Schema Registry 集成
+
+
+
+
vendor: .proto
+base:
+ out: ./gen/go
+ paths: source_relative
+root:
+ - proto
+includes:
+ - proto
+deps:
+ - name: buf/validate
+ source: buf.build
+ url: buf.build/bufbuild/protovalidate
+ - name: googleapis
+ source: buf.build
+ url: buf.build/googleapis/googleapis
+plugins:
+ - name: go
+ - name: go-grpc
+ - name: buf-validate-go
+installers:
+ - go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
+ - go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
+ - go install github.com/bufbuild/buf/cmd/buf@latest
+
+
+
+
+
+
+
🌐 多语言生成
+
同时生成 Go、TypeScript、Python 代码
+
+
+
+
vendor: .proto
+root:
+ - proto
+includes:
+ - proto
+deps:
+ - name: google/protobuf
+ url: https://github.com/protocolbuffers/protobuf
+ path: src/google/protobuf
+plugins:
+ # Go
+ - name: go
+ out: ./gen/go
+ opt:
+ - paths=source_relative
+ - name: go-grpc
+ out: ./gen/go
+ opt:
+ - paths=source_relative
+ # TypeScript (使用 ts-proto)
+ - name: ts_proto
+ out: ./gen/ts
+ opt:
+ - esModuleInterop=true
+ - outputServices=grpc-js
+ # Python
+ - name: python
+ out: ./gen/python
+ - name: grpc_python
+ out: ./gen/python
+installers:
+ - go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
+ - go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
+ - npm install -g ts-proto
+ - pip install grpcio-tools
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/go.mod b/go.mod
index 4b714c2..7cf7416 100644
--- a/go.mod
+++ b/go.mod
@@ -14,6 +14,7 @@ require (
github.com/hashicorp/go-version v1.8.0
github.com/huandu/go-clone v1.7.3
github.com/pubgo/funk v0.5.68
+ github.com/pubgo/protoc-gen-retag v0.0.5
github.com/pubgo/redant v0.0.5
github.com/samber/lo v1.51.0
github.com/schollz/progressbar/v3 v3.19.0
diff --git a/go.sum b/go.sum
index d4582b2..dfc7c62 100644
--- a/go.sum
+++ b/go.sum
@@ -195,6 +195,8 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRI
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pubgo/funk v0.5.68 h1:3fDJAt+QHhPnbAxUr8kLLAh6vra/C3vb7/LoSMQl788=
github.com/pubgo/funk v0.5.68/go.mod h1:CQDKnci4zmCyb0LSD9YiKx/6QBh3Z+PRyCLmJb6ZTOg=
+github.com/pubgo/protoc-gen-retag v0.0.5 h1:tF++mxmAUILb3IkVvSM0el+ww0jcLyPTkIXYzwgiv1A=
+github.com/pubgo/protoc-gen-retag v0.0.5/go.mod h1:/TD5yIvhQpb6ttvpmhpQPUnQ73Rtj23QlXeoczGyAHs=
github.com/pubgo/redant v0.0.5 h1:iDq0cQJNtST8pu9bFSgxZ78JoQ0aVW+svZyOMouWjfM=
github.com/pubgo/redant v0.0.5/go.mod h1:FOBNjL8pPLOBcZS3SL2R5GusFz/bNBwDJzSinGuKs7A=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
diff --git a/internal/config/config.go b/internal/config/config.go
new file mode 100644
index 0000000..cf62cc1
--- /dev/null
+++ b/internal/config/config.go
@@ -0,0 +1,127 @@
+// Package config provides shared configuration types for protobuild.
+package config
+
+// Config represents the protobuild project configuration.
+type Config struct {
+ Checksum string `yaml:"checksum,omitempty" json:"checksum,omitempty" hash:"-"`
+ Vendor string `yaml:"vendor,omitempty" json:"vendor"`
+ BasePlugin *BasePluginCfg `yaml:"base,omitempty" json:"base,omitempty" hash:"-"`
+
+ // Root path, default is proto path (source path)
+ Root []string `yaml:"root,omitempty" json:"root" hash:"-"`
+
+ // Includes protoc include path, default is proto path and .proto path
+ Includes []string `yaml:"includes,omitempty" json:"includes" hash:"-"`
+ Excludes []string `yaml:"excludes,omitempty" json:"excludes" hash:"-"`
+ Depends []*Depend `yaml:"deps,omitempty" json:"deps"`
+ Plugins []*Plugin `yaml:"plugins,omitempty" json:"plugins" hash:"-"`
+ Installers []string `yaml:"installers,omitempty" json:"installers" hash:"-"`
+ Linter *Linter `yaml:"linter,omitempty" json:"linter,omitempty" hash:"-"`
+
+ // Changed is used internally to track if config has been modified (lowercase for internal use)
+ Changed bool `yaml:"-" json:"-"`
+}
+
+// BasePluginCfg represents base plugin configuration applied to all plugins.
+type BasePluginCfg struct {
+ Out string `yaml:"out,omitempty" json:"out"`
+ Paths string `yaml:"paths,omitempty" json:"paths"`
+ Module string `yaml:"module,omitempty" json:"module"`
+}
+
+// Plugin represents a protoc plugin configuration.
+type Plugin struct {
+ // Name protoc plugin name (used as protoc-gen-{name})
+ Name string `yaml:"name,omitempty" json:"name"`
+
+ // Path custom plugin binary path
+ Path string `yaml:"path,omitempty" json:"path,omitempty"`
+
+ // Out output directory
+ Out string `yaml:"out,omitempty" json:"out,omitempty"`
+
+ // Shell run via shell command
+ Shell string `yaml:"shell,omitempty" json:"shell,omitempty"`
+
+ // Docker run via Docker container
+ Docker string `yaml:"docker,omitempty" json:"docker,omitempty"`
+
+ // Remote remote plugin URL
+ Remote string `yaml:"remote,omitempty" json:"remote,omitempty"`
+
+ // SkipBase skip base config
+ SkipBase bool `yaml:"skip_base,omitempty" json:"skip_base,omitempty"`
+
+ // SkipRun skip run plugin
+ SkipRun bool `yaml:"skip_run,omitempty" json:"skip_run,omitempty"`
+
+ // ExcludeOpts options to exclude
+ ExcludeOpts PluginOpts `yaml:"exclude_opts,omitempty" json:"exclude_opts,omitempty"`
+
+ // Opt plugin options
+ Opt PluginOpts `yaml:"opt,omitempty" json:"opt,omitempty"`
+
+ // Opts alias for Opt
+ Opts PluginOpts `yaml:"opts,omitempty" json:"opts,omitempty"`
+}
+
+// Depend represents a proto dependency.
+type Depend struct {
+ // Name local name/path in vendor directory
+ Name string `yaml:"name,omitempty" json:"name"`
+
+ // Source type: gomod(default), git, http, s3, gcs, local
+ Source string `yaml:"source,omitempty" json:"source,omitempty"`
+
+ // Url source URL
+ Url string `yaml:"url,omitempty" json:"url"`
+
+ // Path subdirectory within the source
+ Path string `yaml:"path,omitempty" json:"path,omitempty"`
+
+ // Version specific version (for Go modules)
+ Version *string `yaml:"version,omitempty" json:"version,omitempty"`
+
+ // Ref git ref (branch, tag, commit) for Git sources
+ Ref string `yaml:"ref,omitempty" json:"ref,omitempty"`
+
+ // Optional skip if not found
+ Optional *bool `yaml:"optional,omitempty" json:"optional,omitempty"`
+}
+
+// Linter represents linter configuration.
+type Linter struct {
+ Rules *LinterRules `yaml:"rules,omitempty" json:"rules,omitempty" hash:"-"`
+ FormatType string `yaml:"format_type,omitempty" json:"format_type,omitempty"`
+ IgnoreCommentDisablesFlag bool `yaml:"ignore_comment_disables_flag,omitempty" json:"ignore_comment_disables_flag,omitempty"`
+}
+
+// LinterRules represents linter rules configuration.
+type LinterRules struct {
+ EnabledRules []string `yaml:"enabled_rules,omitempty" json:"enabled_rules,omitempty"`
+ DisabledRules []string `yaml:"disabled_rules,omitempty" json:"disabled_rules,omitempty"`
+}
+
+// GetVersion returns the version string or empty if nil.
+func (d *Depend) GetVersion() string {
+ if d.Version == nil {
+ return ""
+ }
+ return *d.Version
+}
+
+// IsOptional returns true if the dependency is optional.
+func (d *Depend) IsOptional() bool {
+ if d.Optional == nil {
+ return false
+ }
+ return *d.Optional
+}
+
+// GetAllOpts returns combined Opt and Opts.
+func (p *Plugin) GetAllOpts() []string {
+ result := make([]string, 0, len(p.Opt)+len(p.Opts))
+ result = append(result, p.Opt...)
+ result = append(result, p.Opts...)
+ return result
+}
diff --git a/internal/config/loader.go b/internal/config/loader.go
new file mode 100644
index 0000000..8616178
--- /dev/null
+++ b/internal/config/loader.go
@@ -0,0 +1,42 @@
+// Package config provides file loading and saving utilities.
+package config
+
+import (
+ "os"
+
+ "gopkg.in/yaml.v3"
+)
+
+// Load loads configuration from a YAML file.
+func Load(path string) (*Config, error) {
+ data, err := os.ReadFile(path)
+ if err != nil {
+ return nil, err
+ }
+
+ var cfg Config
+ if err := yaml.Unmarshal(data, &cfg); err != nil {
+ return nil, err
+ }
+
+ return &cfg, nil
+}
+
+// Save saves configuration to a YAML file.
+func Save(path string, cfg *Config) error {
+ data, err := yaml.Marshal(cfg)
+ if err != nil {
+ return err
+ }
+
+ return os.WriteFile(path, data, 0644)
+}
+
+// Default returns a default configuration.
+func Default() *Config {
+ return &Config{
+ Vendor: ".proto",
+ Root: []string{"proto"},
+ Includes: []string{"proto", ".proto"},
+ }
+}
diff --git a/internal/config/yaml_types.go b/internal/config/yaml_types.go
new file mode 100644
index 0000000..01882a2
--- /dev/null
+++ b/internal/config/yaml_types.go
@@ -0,0 +1,37 @@
+// Package config provides YAML type helpers for configuration parsing.
+package config
+
+import (
+ "github.com/pubgo/funk/errors"
+ "gopkg.in/yaml.v3"
+)
+
+var _ yaml.Unmarshaler = (*PluginOpts)(nil)
+
+// PluginOpts is a list of plugin options that can be unmarshaled from string or list.
+type PluginOpts []string
+
+// UnmarshalYAML implements yaml.Unmarshaler.
+func (p *PluginOpts) UnmarshalYAML(value *yaml.Node) error {
+ if value.IsZero() {
+ return nil
+ }
+
+ switch value.Kind {
+ case yaml.ScalarNode:
+ if value.Value != "" {
+ *p = []string{value.Value}
+ return nil
+ }
+ return nil
+ case yaml.SequenceNode:
+ var data []string
+ if err := value.Decode(&data); err != nil {
+ return err
+ }
+ *p = data
+ return nil
+ default:
+ return errors.Format("yaml kind type error, kind=%v data=%s", value.Kind, value.Value)
+ }
+}
diff --git a/pkg/example/v1/order.pb.go b/pkg/example/v1/order.pb.go
new file mode 100644
index 0000000..d486b90
--- /dev/null
+++ b/pkg/example/v1/order.pb.go
@@ -0,0 +1,707 @@
+// Code generated by protoc-gen-go. DO NOT EDIT.
+// versions:
+// protoc-gen-go v1.36.10
+// protoc v5.29.3
+// source: example/v1/order.proto
+
+package examplev1
+
+import (
+ protoreflect "google.golang.org/protobuf/reflect/protoreflect"
+ protoimpl "google.golang.org/protobuf/runtime/protoimpl"
+ timestamppb "google.golang.org/protobuf/types/known/timestamppb"
+ reflect "reflect"
+ sync "sync"
+ unsafe "unsafe"
+)
+
+const (
+ // Verify that this generated code is sufficiently up-to-date.
+ _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
+ // Verify that runtime/protoimpl is sufficiently up-to-date.
+ _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
+)
+
+// OrderStatus represents the status of an order.
+type OrderStatus int32
+
+const (
+ OrderStatus_ORDER_STATUS_UNSPECIFIED OrderStatus = 0
+ OrderStatus_ORDER_STATUS_PENDING OrderStatus = 1
+ OrderStatus_ORDER_STATUS_CONFIRMED OrderStatus = 2
+ OrderStatus_ORDER_STATUS_SHIPPED OrderStatus = 3
+ OrderStatus_ORDER_STATUS_DELIVERED OrderStatus = 4
+ OrderStatus_ORDER_STATUS_CANCELLED OrderStatus = 5
+)
+
+// Enum value maps for OrderStatus.
+var (
+ OrderStatus_name = map[int32]string{
+ 0: "ORDER_STATUS_UNSPECIFIED",
+ 1: "ORDER_STATUS_PENDING",
+ 2: "ORDER_STATUS_CONFIRMED",
+ 3: "ORDER_STATUS_SHIPPED",
+ 4: "ORDER_STATUS_DELIVERED",
+ 5: "ORDER_STATUS_CANCELLED",
+ }
+ OrderStatus_value = map[string]int32{
+ "ORDER_STATUS_UNSPECIFIED": 0,
+ "ORDER_STATUS_PENDING": 1,
+ "ORDER_STATUS_CONFIRMED": 2,
+ "ORDER_STATUS_SHIPPED": 3,
+ "ORDER_STATUS_DELIVERED": 4,
+ "ORDER_STATUS_CANCELLED": 5,
+ }
+)
+
+func (x OrderStatus) Enum() *OrderStatus {
+ p := new(OrderStatus)
+ *p = x
+ return p
+}
+
+func (x OrderStatus) String() string {
+ return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
+}
+
+func (OrderStatus) Descriptor() protoreflect.EnumDescriptor {
+ return file_example_v1_order_proto_enumTypes[0].Descriptor()
+}
+
+func (OrderStatus) Type() protoreflect.EnumType {
+ return &file_example_v1_order_proto_enumTypes[0]
+}
+
+func (x OrderStatus) Number() protoreflect.EnumNumber {
+ return protoreflect.EnumNumber(x)
+}
+
+// Deprecated: Use OrderStatus.Descriptor instead.
+func (OrderStatus) EnumDescriptor() ([]byte, []int) {
+ return file_example_v1_order_proto_rawDescGZIP(), []int{0}
+}
+
+// Order represents an order in the system.
+type Order struct {
+ state protoimpl.MessageState `protogen:"open.v1"`
+ Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
+ UserId string `protobuf:"bytes,2,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"`
+ Items []*OrderItem `protobuf:"bytes,3,rep,name=items,proto3" json:"items,omitempty"`
+ Status OrderStatus `protobuf:"varint,4,opt,name=status,proto3,enum=example.v1.OrderStatus" json:"status,omitempty"`
+ CreatedAt *timestamppb.Timestamp `protobuf:"bytes,5,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"`
+ UpdatedAt *timestamppb.Timestamp `protobuf:"bytes,6,opt,name=updated_at,json=updatedAt,proto3" json:"updated_at,omitempty"`
+ Total *Money `protobuf:"bytes,7,opt,name=total,proto3" json:"total,omitempty"`
+ ShippingAddress *ShippingAddress `protobuf:"bytes,8,opt,name=shipping_address,json=shippingAddress,proto3" json:"shipping_address,omitempty"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *Order) Reset() {
+ *x = Order{}
+ mi := &file_example_v1_order_proto_msgTypes[0]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *Order) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*Order) ProtoMessage() {}
+
+func (x *Order) ProtoReflect() protoreflect.Message {
+ mi := &file_example_v1_order_proto_msgTypes[0]
+ if x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use Order.ProtoReflect.Descriptor instead.
+func (*Order) Descriptor() ([]byte, []int) {
+ return file_example_v1_order_proto_rawDescGZIP(), []int{0}
+}
+
+func (x *Order) GetId() string {
+ if x != nil {
+ return x.Id
+ }
+ return ""
+}
+
+func (x *Order) GetUserId() string {
+ if x != nil {
+ return x.UserId
+ }
+ return ""
+}
+
+func (x *Order) GetItems() []*OrderItem {
+ if x != nil {
+ return x.Items
+ }
+ return nil
+}
+
+func (x *Order) GetStatus() OrderStatus {
+ if x != nil {
+ return x.Status
+ }
+ return OrderStatus_ORDER_STATUS_UNSPECIFIED
+}
+
+func (x *Order) GetCreatedAt() *timestamppb.Timestamp {
+ if x != nil {
+ return x.CreatedAt
+ }
+ return nil
+}
+
+func (x *Order) GetUpdatedAt() *timestamppb.Timestamp {
+ if x != nil {
+ return x.UpdatedAt
+ }
+ return nil
+}
+
+func (x *Order) GetTotal() *Money {
+ if x != nil {
+ return x.Total
+ }
+ return nil
+}
+
+func (x *Order) GetShippingAddress() *ShippingAddress {
+ if x != nil {
+ return x.ShippingAddress
+ }
+ return nil
+}
+
+// OrderItem represents an item in an order.
+type OrderItem struct {
+ state protoimpl.MessageState `protogen:"open.v1"`
+ ProductId string `protobuf:"bytes,1,opt,name=product_id,json=productId,proto3" json:"product_id,omitempty"`
+ ProductName string `protobuf:"bytes,2,opt,name=product_name,json=productName,proto3" json:"product_name,omitempty"`
+ Quantity int32 `protobuf:"varint,3,opt,name=quantity,proto3" json:"quantity,omitempty"`
+ UnitPrice *Money `protobuf:"bytes,4,opt,name=unit_price,json=unitPrice,proto3" json:"unit_price,omitempty"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *OrderItem) Reset() {
+ *x = OrderItem{}
+ mi := &file_example_v1_order_proto_msgTypes[1]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *OrderItem) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*OrderItem) ProtoMessage() {}
+
+func (x *OrderItem) ProtoReflect() protoreflect.Message {
+ mi := &file_example_v1_order_proto_msgTypes[1]
+ if x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use OrderItem.ProtoReflect.Descriptor instead.
+func (*OrderItem) Descriptor() ([]byte, []int) {
+ return file_example_v1_order_proto_rawDescGZIP(), []int{1}
+}
+
+func (x *OrderItem) GetProductId() string {
+ if x != nil {
+ return x.ProductId
+ }
+ return ""
+}
+
+func (x *OrderItem) GetProductName() string {
+ if x != nil {
+ return x.ProductName
+ }
+ return ""
+}
+
+func (x *OrderItem) GetQuantity() int32 {
+ if x != nil {
+ return x.Quantity
+ }
+ return 0
+}
+
+func (x *OrderItem) GetUnitPrice() *Money {
+ if x != nil {
+ return x.UnitPrice
+ }
+ return nil
+}
+
+// Money represents a monetary value.
+type Money struct {
+ state protoimpl.MessageState `protogen:"open.v1"`
+ CurrencyCode string `protobuf:"bytes,1,opt,name=currency_code,json=currencyCode,proto3" json:"currency_code,omitempty"`
+ Units int64 `protobuf:"varint,2,opt,name=units,proto3" json:"units,omitempty"`
+ Nanos int32 `protobuf:"varint,3,opt,name=nanos,proto3" json:"nanos,omitempty"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *Money) Reset() {
+ *x = Money{}
+ mi := &file_example_v1_order_proto_msgTypes[2]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *Money) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*Money) ProtoMessage() {}
+
+func (x *Money) ProtoReflect() protoreflect.Message {
+ mi := &file_example_v1_order_proto_msgTypes[2]
+ if x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use Money.ProtoReflect.Descriptor instead.
+func (*Money) Descriptor() ([]byte, []int) {
+ return file_example_v1_order_proto_rawDescGZIP(), []int{2}
+}
+
+func (x *Money) GetCurrencyCode() string {
+ if x != nil {
+ return x.CurrencyCode
+ }
+ return ""
+}
+
+func (x *Money) GetUnits() int64 {
+ if x != nil {
+ return x.Units
+ }
+ return 0
+}
+
+func (x *Money) GetNanos() int32 {
+ if x != nil {
+ return x.Nanos
+ }
+ return 0
+}
+
+// ShippingAddress represents a shipping address.
+type ShippingAddress struct {
+ state protoimpl.MessageState `protogen:"open.v1"`
+ Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
+ Street string `protobuf:"bytes,2,opt,name=street,proto3" json:"street,omitempty"`
+ City string `protobuf:"bytes,3,opt,name=city,proto3" json:"city,omitempty"`
+ State string `protobuf:"bytes,4,opt,name=state,proto3" json:"state,omitempty"`
+ PostalCode string `protobuf:"bytes,5,opt,name=postal_code,json=postalCode,proto3" json:"postal_code,omitempty"`
+ Country string `protobuf:"bytes,6,opt,name=country,proto3" json:"country,omitempty"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *ShippingAddress) Reset() {
+ *x = ShippingAddress{}
+ mi := &file_example_v1_order_proto_msgTypes[3]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *ShippingAddress) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*ShippingAddress) ProtoMessage() {}
+
+func (x *ShippingAddress) ProtoReflect() protoreflect.Message {
+ mi := &file_example_v1_order_proto_msgTypes[3]
+ if x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use ShippingAddress.ProtoReflect.Descriptor instead.
+func (*ShippingAddress) Descriptor() ([]byte, []int) {
+ return file_example_v1_order_proto_rawDescGZIP(), []int{3}
+}
+
+func (x *ShippingAddress) GetName() string {
+ if x != nil {
+ return x.Name
+ }
+ return ""
+}
+
+func (x *ShippingAddress) GetStreet() string {
+ if x != nil {
+ return x.Street
+ }
+ return ""
+}
+
+func (x *ShippingAddress) GetCity() string {
+ if x != nil {
+ return x.City
+ }
+ return ""
+}
+
+func (x *ShippingAddress) GetState() string {
+ if x != nil {
+ return x.State
+ }
+ return ""
+}
+
+func (x *ShippingAddress) GetPostalCode() string {
+ if x != nil {
+ return x.PostalCode
+ }
+ return ""
+}
+
+func (x *ShippingAddress) GetCountry() string {
+ if x != nil {
+ return x.Country
+ }
+ return ""
+}
+
+// CreateOrderRequest is the request for creating an order.
+type CreateOrderRequest struct {
+ state protoimpl.MessageState `protogen:"open.v1"`
+ Order *Order `protobuf:"bytes,1,opt,name=order,proto3" json:"order,omitempty"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *CreateOrderRequest) Reset() {
+ *x = CreateOrderRequest{}
+ mi := &file_example_v1_order_proto_msgTypes[4]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *CreateOrderRequest) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*CreateOrderRequest) ProtoMessage() {}
+
+func (x *CreateOrderRequest) ProtoReflect() protoreflect.Message {
+ mi := &file_example_v1_order_proto_msgTypes[4]
+ if x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use CreateOrderRequest.ProtoReflect.Descriptor instead.
+func (*CreateOrderRequest) Descriptor() ([]byte, []int) {
+ return file_example_v1_order_proto_rawDescGZIP(), []int{4}
+}
+
+func (x *CreateOrderRequest) GetOrder() *Order {
+ if x != nil {
+ return x.Order
+ }
+ return nil
+}
+
+// CreateOrderResponse is the response for creating an order.
+type CreateOrderResponse struct {
+ state protoimpl.MessageState `protogen:"open.v1"`
+ Order *Order `protobuf:"bytes,1,opt,name=order,proto3" json:"order,omitempty"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *CreateOrderResponse) Reset() {
+ *x = CreateOrderResponse{}
+ mi := &file_example_v1_order_proto_msgTypes[5]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *CreateOrderResponse) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*CreateOrderResponse) ProtoMessage() {}
+
+func (x *CreateOrderResponse) ProtoReflect() protoreflect.Message {
+ mi := &file_example_v1_order_proto_msgTypes[5]
+ if x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use CreateOrderResponse.ProtoReflect.Descriptor instead.
+func (*CreateOrderResponse) Descriptor() ([]byte, []int) {
+ return file_example_v1_order_proto_rawDescGZIP(), []int{5}
+}
+
+func (x *CreateOrderResponse) GetOrder() *Order {
+ if x != nil {
+ return x.Order
+ }
+ return nil
+}
+
+// GetOrderRequest is the request for getting an order.
+type GetOrderRequest struct {
+ state protoimpl.MessageState `protogen:"open.v1"`
+ Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *GetOrderRequest) Reset() {
+ *x = GetOrderRequest{}
+ mi := &file_example_v1_order_proto_msgTypes[6]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *GetOrderRequest) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*GetOrderRequest) ProtoMessage() {}
+
+func (x *GetOrderRequest) ProtoReflect() protoreflect.Message {
+ mi := &file_example_v1_order_proto_msgTypes[6]
+ if x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use GetOrderRequest.ProtoReflect.Descriptor instead.
+func (*GetOrderRequest) Descriptor() ([]byte, []int) {
+ return file_example_v1_order_proto_rawDescGZIP(), []int{6}
+}
+
+func (x *GetOrderRequest) GetId() string {
+ if x != nil {
+ return x.Id
+ }
+ return ""
+}
+
+// GetOrderResponse is the response for getting an order.
+type GetOrderResponse struct {
+ state protoimpl.MessageState `protogen:"open.v1"`
+ Order *Order `protobuf:"bytes,1,opt,name=order,proto3" json:"order,omitempty"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *GetOrderResponse) Reset() {
+ *x = GetOrderResponse{}
+ mi := &file_example_v1_order_proto_msgTypes[7]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *GetOrderResponse) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*GetOrderResponse) ProtoMessage() {}
+
+func (x *GetOrderResponse) ProtoReflect() protoreflect.Message {
+ mi := &file_example_v1_order_proto_msgTypes[7]
+ if x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use GetOrderResponse.ProtoReflect.Descriptor instead.
+func (*GetOrderResponse) Descriptor() ([]byte, []int) {
+ return file_example_v1_order_proto_rawDescGZIP(), []int{7}
+}
+
+func (x *GetOrderResponse) GetOrder() *Order {
+ if x != nil {
+ return x.Order
+ }
+ return nil
+}
+
+var File_example_v1_order_proto protoreflect.FileDescriptor
+
+const file_example_v1_order_proto_rawDesc = "" +
+ "\n" +
+ "\x16example/v1/order.proto\x12\n" +
+ "example.v1\x1a\x1fgoogle/protobuf/timestamp.proto\"\xf5\x02\n" +
+ "\x05Order\x12\x0e\n" +
+ "\x02id\x18\x01 \x01(\tR\x02id\x12\x17\n" +
+ "\auser_id\x18\x02 \x01(\tR\x06userId\x12+\n" +
+ "\x05items\x18\x03 \x03(\v2\x15.example.v1.OrderItemR\x05items\x12/\n" +
+ "\x06status\x18\x04 \x01(\x0e2\x17.example.v1.OrderStatusR\x06status\x129\n" +
+ "\n" +
+ "created_at\x18\x05 \x01(\v2\x1a.google.protobuf.TimestampR\tcreatedAt\x129\n" +
+ "\n" +
+ "updated_at\x18\x06 \x01(\v2\x1a.google.protobuf.TimestampR\tupdatedAt\x12'\n" +
+ "\x05total\x18\a \x01(\v2\x11.example.v1.MoneyR\x05total\x12F\n" +
+ "\x10shipping_address\x18\b \x01(\v2\x1b.example.v1.ShippingAddressR\x0fshippingAddress\"\x9b\x01\n" +
+ "\tOrderItem\x12\x1d\n" +
+ "\n" +
+ "product_id\x18\x01 \x01(\tR\tproductId\x12!\n" +
+ "\fproduct_name\x18\x02 \x01(\tR\vproductName\x12\x1a\n" +
+ "\bquantity\x18\x03 \x01(\x05R\bquantity\x120\n" +
+ "\n" +
+ "unit_price\x18\x04 \x01(\v2\x11.example.v1.MoneyR\tunitPrice\"X\n" +
+ "\x05Money\x12#\n" +
+ "\rcurrency_code\x18\x01 \x01(\tR\fcurrencyCode\x12\x14\n" +
+ "\x05units\x18\x02 \x01(\x03R\x05units\x12\x14\n" +
+ "\x05nanos\x18\x03 \x01(\x05R\x05nanos\"\xa2\x01\n" +
+ "\x0fShippingAddress\x12\x12\n" +
+ "\x04name\x18\x01 \x01(\tR\x04name\x12\x16\n" +
+ "\x06street\x18\x02 \x01(\tR\x06street\x12\x12\n" +
+ "\x04city\x18\x03 \x01(\tR\x04city\x12\x14\n" +
+ "\x05state\x18\x04 \x01(\tR\x05state\x12\x1f\n" +
+ "\vpostal_code\x18\x05 \x01(\tR\n" +
+ "postalCode\x12\x18\n" +
+ "\acountry\x18\x06 \x01(\tR\acountry\"=\n" +
+ "\x12CreateOrderRequest\x12'\n" +
+ "\x05order\x18\x01 \x01(\v2\x11.example.v1.OrderR\x05order\">\n" +
+ "\x13CreateOrderResponse\x12'\n" +
+ "\x05order\x18\x01 \x01(\v2\x11.example.v1.OrderR\x05order\"!\n" +
+ "\x0fGetOrderRequest\x12\x0e\n" +
+ "\x02id\x18\x01 \x01(\tR\x02id\";\n" +
+ "\x10GetOrderResponse\x12'\n" +
+ "\x05order\x18\x01 \x01(\v2\x11.example.v1.OrderR\x05order*\xb3\x01\n" +
+ "\vOrderStatus\x12\x1c\n" +
+ "\x18ORDER_STATUS_UNSPECIFIED\x10\x00\x12\x18\n" +
+ "\x14ORDER_STATUS_PENDING\x10\x01\x12\x1a\n" +
+ "\x16ORDER_STATUS_CONFIRMED\x10\x02\x12\x18\n" +
+ "\x14ORDER_STATUS_SHIPPED\x10\x03\x12\x1a\n" +
+ "\x16ORDER_STATUS_DELIVERED\x10\x04\x12\x1a\n" +
+ "\x16ORDER_STATUS_CANCELLED\x10\x052\xa5\x01\n" +
+ "\fOrderService\x12N\n" +
+ "\vCreateOrder\x12\x1e.example.v1.CreateOrderRequest\x1a\x1f.example.v1.CreateOrderResponse\x12E\n" +
+ "\bGetOrder\x12\x1b.example.v1.GetOrderRequest\x1a\x1c.example.v1.GetOrderResponseBH\n" +
+ "\x0ecom.example.v1P\x01Z4github.com/pubgo/protobuild/pkg/example/v1;examplev1b\x06proto3"
+
+var (
+ file_example_v1_order_proto_rawDescOnce sync.Once
+ file_example_v1_order_proto_rawDescData []byte
+)
+
+func file_example_v1_order_proto_rawDescGZIP() []byte {
+ file_example_v1_order_proto_rawDescOnce.Do(func() {
+ file_example_v1_order_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_example_v1_order_proto_rawDesc), len(file_example_v1_order_proto_rawDesc)))
+ })
+ return file_example_v1_order_proto_rawDescData
+}
+
+var file_example_v1_order_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
+var file_example_v1_order_proto_msgTypes = make([]protoimpl.MessageInfo, 8)
+var file_example_v1_order_proto_goTypes = []any{
+ (OrderStatus)(0), // 0: example.v1.OrderStatus
+ (*Order)(nil), // 1: example.v1.Order
+ (*OrderItem)(nil), // 2: example.v1.OrderItem
+ (*Money)(nil), // 3: example.v1.Money
+ (*ShippingAddress)(nil), // 4: example.v1.ShippingAddress
+ (*CreateOrderRequest)(nil), // 5: example.v1.CreateOrderRequest
+ (*CreateOrderResponse)(nil), // 6: example.v1.CreateOrderResponse
+ (*GetOrderRequest)(nil), // 7: example.v1.GetOrderRequest
+ (*GetOrderResponse)(nil), // 8: example.v1.GetOrderResponse
+ (*timestamppb.Timestamp)(nil), // 9: google.protobuf.Timestamp
+}
+var file_example_v1_order_proto_depIdxs = []int32{
+ 2, // 0: example.v1.Order.items:type_name -> example.v1.OrderItem
+ 0, // 1: example.v1.Order.status:type_name -> example.v1.OrderStatus
+ 9, // 2: example.v1.Order.created_at:type_name -> google.protobuf.Timestamp
+ 9, // 3: example.v1.Order.updated_at:type_name -> google.protobuf.Timestamp
+ 3, // 4: example.v1.Order.total:type_name -> example.v1.Money
+ 4, // 5: example.v1.Order.shipping_address:type_name -> example.v1.ShippingAddress
+ 3, // 6: example.v1.OrderItem.unit_price:type_name -> example.v1.Money
+ 1, // 7: example.v1.CreateOrderRequest.order:type_name -> example.v1.Order
+ 1, // 8: example.v1.CreateOrderResponse.order:type_name -> example.v1.Order
+ 1, // 9: example.v1.GetOrderResponse.order:type_name -> example.v1.Order
+ 5, // 10: example.v1.OrderService.CreateOrder:input_type -> example.v1.CreateOrderRequest
+ 7, // 11: example.v1.OrderService.GetOrder:input_type -> example.v1.GetOrderRequest
+ 6, // 12: example.v1.OrderService.CreateOrder:output_type -> example.v1.CreateOrderResponse
+ 8, // 13: example.v1.OrderService.GetOrder:output_type -> example.v1.GetOrderResponse
+ 12, // [12:14] is the sub-list for method output_type
+ 10, // [10:12] is the sub-list for method input_type
+ 10, // [10:10] is the sub-list for extension type_name
+ 10, // [10:10] is the sub-list for extension extendee
+ 0, // [0:10] is the sub-list for field type_name
+}
+
+func init() { file_example_v1_order_proto_init() }
+func file_example_v1_order_proto_init() {
+ if File_example_v1_order_proto != nil {
+ return
+ }
+ type x struct{}
+ out := protoimpl.TypeBuilder{
+ File: protoimpl.DescBuilder{
+ GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
+ RawDescriptor: unsafe.Slice(unsafe.StringData(file_example_v1_order_proto_rawDesc), len(file_example_v1_order_proto_rawDesc)),
+ NumEnums: 1,
+ NumMessages: 8,
+ NumExtensions: 0,
+ NumServices: 1,
+ },
+ GoTypes: file_example_v1_order_proto_goTypes,
+ DependencyIndexes: file_example_v1_order_proto_depIdxs,
+ EnumInfos: file_example_v1_order_proto_enumTypes,
+ MessageInfos: file_example_v1_order_proto_msgTypes,
+ }.Build()
+ File_example_v1_order_proto = out.File
+ file_example_v1_order_proto_goTypes = nil
+ file_example_v1_order_proto_depIdxs = nil
+}
diff --git a/pkg/example/v1/user.pb.go b/pkg/example/v1/user.pb.go
new file mode 100644
index 0000000..7512265
--- /dev/null
+++ b/pkg/example/v1/user.pb.go
@@ -0,0 +1,882 @@
+// Code generated by protoc-gen-retag. DO NOT EDIT.
+// Code generated by protoc-gen-go. DO NOT EDIT.
+// versions:
+// protoc-gen-go v1.36.10
+// protoc v5.29.3
+// source: example/v1/user.proto
+
+package examplev1
+
+import (
+ _ "github.com/pubgo/protoc-gen-retag/pkg/retag"
+ protoreflect "google.golang.org/protobuf/reflect/protoreflect"
+ protoimpl "google.golang.org/protobuf/runtime/protoimpl"
+ fieldmaskpb "google.golang.org/protobuf/types/known/fieldmaskpb"
+ timestamppb "google.golang.org/protobuf/types/known/timestamppb"
+ reflect "reflect"
+ sync "sync"
+ unsafe "unsafe"
+)
+
+const (
+ // Verify that this generated code is sufficiently up-to-date.
+ _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
+ // Verify that runtime/protoimpl is sufficiently up-to-date.
+ _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
+)
+
+// UserStatus represents the status of a user.
+type UserStatus int32
+
+const (
+ UserStatus_USER_STATUS_UNSPECIFIED UserStatus = 0
+ UserStatus_USER_STATUS_ACTIVE UserStatus = 1
+ UserStatus_USER_STATUS_INACTIVE UserStatus = 2
+ UserStatus_USER_STATUS_BANNED UserStatus = 3
+)
+
+// Enum value maps for UserStatus.
+var (
+ UserStatus_name = map[int32]string{
+ 0: "USER_STATUS_UNSPECIFIED",
+ 1: "USER_STATUS_ACTIVE",
+ 2: "USER_STATUS_INACTIVE",
+ 3: "USER_STATUS_BANNED",
+ }
+ UserStatus_value = map[string]int32{
+ "USER_STATUS_UNSPECIFIED": 0,
+ "USER_STATUS_ACTIVE": 1,
+ "USER_STATUS_INACTIVE": 2,
+ "USER_STATUS_BANNED": 3,
+ }
+)
+
+func (x UserStatus) Enum() *UserStatus {
+ p := new(UserStatus)
+ *p = x
+ return p
+}
+
+func (x UserStatus) String() string {
+ return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
+}
+
+func (UserStatus) Descriptor() protoreflect.EnumDescriptor {
+ return file_example_v1_user_proto_enumTypes[0].Descriptor()
+}
+
+func (UserStatus) Type() protoreflect.EnumType {
+ return &file_example_v1_user_proto_enumTypes[0]
+}
+
+func (x UserStatus) Number() protoreflect.EnumNumber {
+ return protoreflect.EnumNumber(x)
+}
+
+// Deprecated: Use UserStatus.Descriptor instead.
+func (UserStatus) EnumDescriptor() ([]byte, []int) {
+ return file_example_v1_user_proto_rawDescGZIP(), []int{0}
+}
+
+// User represents a user in the system.
+type User struct {
+ state protoimpl.MessageState `protogen:"open.v1"`
+ // Unique identifier for the user.
+ Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty" xml:"id,attr"`
+ // User's display name.
+ Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty" xml:"name"`
+ // User's email address.
+ Email string `protobuf:"bytes,3,opt,name=email,proto3" json:"email,omitempty" xml:"email" validate:"required,email"`
+ // User's age.
+ Age int32 `protobuf:"varint,4,opt,name=age,proto3" json:"age,omitempty" xml:"age,omitempty"`
+ // When the user was created.
+ CreatedAt *timestamppb.Timestamp `protobuf:"bytes,5,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"`
+ // When the user was last updated.
+ UpdatedAt *timestamppb.Timestamp `protobuf:"bytes,6,opt,name=updated_at,json=updatedAt,proto3" json:"updated_at,omitempty"`
+ // User's status.
+ Status UserStatus `protobuf:"varint,7,opt,name=status,proto3,enum=example.v1.UserStatus" json:"status,omitempty"`
+ // User's profile.
+ Profile *Profile `protobuf:"bytes,8,opt,name=profile,proto3" json:"profile,omitempty"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *User) Reset() {
+ *x = User{}
+ mi := &file_example_v1_user_proto_msgTypes[0]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *User) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*User) ProtoMessage() {}
+
+func (x *User) ProtoReflect() protoreflect.Message {
+ mi := &file_example_v1_user_proto_msgTypes[0]
+ if x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use User.ProtoReflect.Descriptor instead.
+func (*User) Descriptor() ([]byte, []int) {
+ return file_example_v1_user_proto_rawDescGZIP(), []int{0}
+}
+
+func (x *User) GetId() string {
+ if x != nil {
+ return x.Id
+ }
+ return ""
+}
+
+func (x *User) GetName() string {
+ if x != nil {
+ return x.Name
+ }
+ return ""
+}
+
+func (x *User) GetEmail() string {
+ if x != nil {
+ return x.Email
+ }
+ return ""
+}
+
+func (x *User) GetAge() int32 {
+ if x != nil {
+ return x.Age
+ }
+ return 0
+}
+
+func (x *User) GetCreatedAt() *timestamppb.Timestamp {
+ if x != nil {
+ return x.CreatedAt
+ }
+ return nil
+}
+
+func (x *User) GetUpdatedAt() *timestamppb.Timestamp {
+ if x != nil {
+ return x.UpdatedAt
+ }
+ return nil
+}
+
+func (x *User) GetStatus() UserStatus {
+ if x != nil {
+ return x.Status
+ }
+ return UserStatus_USER_STATUS_UNSPECIFIED
+}
+
+func (x *User) GetProfile() *Profile {
+ if x != nil {
+ return x.Profile
+ }
+ return nil
+}
+
+// Profile contains user profile information.
+type Profile struct {
+ state protoimpl.MessageState `protogen:"open.v1"`
+ AvatarUrl string `protobuf:"bytes,1,opt,name=avatar_url,json=avatarUrl,proto3" json:"avatar_url,omitempty" xml:"avatarUrl"`
+ Bio string `protobuf:"bytes,2,opt,name=bio,proto3" json:"bio,omitempty" xml:"bio,omitempty"`
+ Location string `protobuf:"bytes,3,opt,name=location,proto3" json:"location,omitempty" xml:"location,omitempty"`
+ Website string `protobuf:"bytes,4,opt,name=website,proto3" json:"website,omitempty" xml:"website,omitempty" validate:"omitempty,url"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *Profile) Reset() {
+ *x = Profile{}
+ mi := &file_example_v1_user_proto_msgTypes[1]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *Profile) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*Profile) ProtoMessage() {}
+
+func (x *Profile) ProtoReflect() protoreflect.Message {
+ mi := &file_example_v1_user_proto_msgTypes[1]
+ if x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use Profile.ProtoReflect.Descriptor instead.
+func (*Profile) Descriptor() ([]byte, []int) {
+ return file_example_v1_user_proto_rawDescGZIP(), []int{1}
+}
+
+func (x *Profile) GetAvatarUrl() string {
+ if x != nil {
+ return x.AvatarUrl
+ }
+ return ""
+}
+
+func (x *Profile) GetBio() string {
+ if x != nil {
+ return x.Bio
+ }
+ return ""
+}
+
+func (x *Profile) GetLocation() string {
+ if x != nil {
+ return x.Location
+ }
+ return ""
+}
+
+func (x *Profile) GetWebsite() string {
+ if x != nil {
+ return x.Website
+ }
+ return ""
+}
+
+// CreateUserRequest is the request for creating a user.
+type CreateUserRequest struct {
+ state protoimpl.MessageState `protogen:"open.v1"`
+ User *User `protobuf:"bytes,1,opt,name=user,proto3" json:"user,omitempty"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *CreateUserRequest) Reset() {
+ *x = CreateUserRequest{}
+ mi := &file_example_v1_user_proto_msgTypes[2]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *CreateUserRequest) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*CreateUserRequest) ProtoMessage() {}
+
+func (x *CreateUserRequest) ProtoReflect() protoreflect.Message {
+ mi := &file_example_v1_user_proto_msgTypes[2]
+ if x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use CreateUserRequest.ProtoReflect.Descriptor instead.
+func (*CreateUserRequest) Descriptor() ([]byte, []int) {
+ return file_example_v1_user_proto_rawDescGZIP(), []int{2}
+}
+
+func (x *CreateUserRequest) GetUser() *User {
+ if x != nil {
+ return x.User
+ }
+ return nil
+}
+
+// CreateUserResponse is the response for creating a user.
+type CreateUserResponse struct {
+ state protoimpl.MessageState `protogen:"open.v1"`
+ User *User `protobuf:"bytes,1,opt,name=user,proto3" json:"user,omitempty"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *CreateUserResponse) Reset() {
+ *x = CreateUserResponse{}
+ mi := &file_example_v1_user_proto_msgTypes[3]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *CreateUserResponse) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*CreateUserResponse) ProtoMessage() {}
+
+func (x *CreateUserResponse) ProtoReflect() protoreflect.Message {
+ mi := &file_example_v1_user_proto_msgTypes[3]
+ if x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use CreateUserResponse.ProtoReflect.Descriptor instead.
+func (*CreateUserResponse) Descriptor() ([]byte, []int) {
+ return file_example_v1_user_proto_rawDescGZIP(), []int{3}
+}
+
+func (x *CreateUserResponse) GetUser() *User {
+ if x != nil {
+ return x.User
+ }
+ return nil
+}
+
+// GetUserRequest is the request for getting a user.
+type GetUserRequest struct {
+ state protoimpl.MessageState `protogen:"open.v1"`
+ Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *GetUserRequest) Reset() {
+ *x = GetUserRequest{}
+ mi := &file_example_v1_user_proto_msgTypes[4]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *GetUserRequest) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*GetUserRequest) ProtoMessage() {}
+
+func (x *GetUserRequest) ProtoReflect() protoreflect.Message {
+ mi := &file_example_v1_user_proto_msgTypes[4]
+ if x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use GetUserRequest.ProtoReflect.Descriptor instead.
+func (*GetUserRequest) Descriptor() ([]byte, []int) {
+ return file_example_v1_user_proto_rawDescGZIP(), []int{4}
+}
+
+func (x *GetUserRequest) GetId() string {
+ if x != nil {
+ return x.Id
+ }
+ return ""
+}
+
+// GetUserResponse is the response for getting a user.
+type GetUserResponse struct {
+ state protoimpl.MessageState `protogen:"open.v1"`
+ User *User `protobuf:"bytes,1,opt,name=user,proto3" json:"user,omitempty"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *GetUserResponse) Reset() {
+ *x = GetUserResponse{}
+ mi := &file_example_v1_user_proto_msgTypes[5]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *GetUserResponse) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*GetUserResponse) ProtoMessage() {}
+
+func (x *GetUserResponse) ProtoReflect() protoreflect.Message {
+ mi := &file_example_v1_user_proto_msgTypes[5]
+ if x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use GetUserResponse.ProtoReflect.Descriptor instead.
+func (*GetUserResponse) Descriptor() ([]byte, []int) {
+ return file_example_v1_user_proto_rawDescGZIP(), []int{5}
+}
+
+func (x *GetUserResponse) GetUser() *User {
+ if x != nil {
+ return x.User
+ }
+ return nil
+}
+
+// UpdateUserRequest is the request for updating a user.
+type UpdateUserRequest struct {
+ state protoimpl.MessageState `protogen:"open.v1"`
+ User *User `protobuf:"bytes,1,opt,name=user,proto3" json:"user,omitempty"`
+ UpdateMask *fieldmaskpb.FieldMask `protobuf:"bytes,2,opt,name=update_mask,json=updateMask,proto3" json:"update_mask,omitempty"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *UpdateUserRequest) Reset() {
+ *x = UpdateUserRequest{}
+ mi := &file_example_v1_user_proto_msgTypes[6]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *UpdateUserRequest) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*UpdateUserRequest) ProtoMessage() {}
+
+func (x *UpdateUserRequest) ProtoReflect() protoreflect.Message {
+ mi := &file_example_v1_user_proto_msgTypes[6]
+ if x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use UpdateUserRequest.ProtoReflect.Descriptor instead.
+func (*UpdateUserRequest) Descriptor() ([]byte, []int) {
+ return file_example_v1_user_proto_rawDescGZIP(), []int{6}
+}
+
+func (x *UpdateUserRequest) GetUser() *User {
+ if x != nil {
+ return x.User
+ }
+ return nil
+}
+
+func (x *UpdateUserRequest) GetUpdateMask() *fieldmaskpb.FieldMask {
+ if x != nil {
+ return x.UpdateMask
+ }
+ return nil
+}
+
+// UpdateUserResponse is the response for updating a user.
+type UpdateUserResponse struct {
+ state protoimpl.MessageState `protogen:"open.v1"`
+ User *User `protobuf:"bytes,1,opt,name=user,proto3" json:"user,omitempty"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *UpdateUserResponse) Reset() {
+ *x = UpdateUserResponse{}
+ mi := &file_example_v1_user_proto_msgTypes[7]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *UpdateUserResponse) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*UpdateUserResponse) ProtoMessage() {}
+
+func (x *UpdateUserResponse) ProtoReflect() protoreflect.Message {
+ mi := &file_example_v1_user_proto_msgTypes[7]
+ if x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use UpdateUserResponse.ProtoReflect.Descriptor instead.
+func (*UpdateUserResponse) Descriptor() ([]byte, []int) {
+ return file_example_v1_user_proto_rawDescGZIP(), []int{7}
+}
+
+func (x *UpdateUserResponse) GetUser() *User {
+ if x != nil {
+ return x.User
+ }
+ return nil
+}
+
+// DeleteUserRequest is the request for deleting a user.
+type DeleteUserRequest struct {
+ state protoimpl.MessageState `protogen:"open.v1"`
+ Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *DeleteUserRequest) Reset() {
+ *x = DeleteUserRequest{}
+ mi := &file_example_v1_user_proto_msgTypes[8]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *DeleteUserRequest) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*DeleteUserRequest) ProtoMessage() {}
+
+func (x *DeleteUserRequest) ProtoReflect() protoreflect.Message {
+ mi := &file_example_v1_user_proto_msgTypes[8]
+ if x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use DeleteUserRequest.ProtoReflect.Descriptor instead.
+func (*DeleteUserRequest) Descriptor() ([]byte, []int) {
+ return file_example_v1_user_proto_rawDescGZIP(), []int{8}
+}
+
+func (x *DeleteUserRequest) GetId() string {
+ if x != nil {
+ return x.Id
+ }
+ return ""
+}
+
+// DeleteUserResponse is the response for deleting a user.
+type DeleteUserResponse struct {
+ state protoimpl.MessageState `protogen:"open.v1"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *DeleteUserResponse) Reset() {
+ *x = DeleteUserResponse{}
+ mi := &file_example_v1_user_proto_msgTypes[9]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *DeleteUserResponse) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*DeleteUserResponse) ProtoMessage() {}
+
+func (x *DeleteUserResponse) ProtoReflect() protoreflect.Message {
+ mi := &file_example_v1_user_proto_msgTypes[9]
+ if x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use DeleteUserResponse.ProtoReflect.Descriptor instead.
+func (*DeleteUserResponse) Descriptor() ([]byte, []int) {
+ return file_example_v1_user_proto_rawDescGZIP(), []int{9}
+}
+
+// ListUsersRequest is the request for listing users.
+type ListUsersRequest struct {
+ state protoimpl.MessageState `protogen:"open.v1"`
+ PageSize int32 `protobuf:"varint,1,opt,name=page_size,json=pageSize,proto3" json:"page_size,omitempty"`
+ PageToken string `protobuf:"bytes,2,opt,name=page_token,json=pageToken,proto3" json:"page_token,omitempty"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *ListUsersRequest) Reset() {
+ *x = ListUsersRequest{}
+ mi := &file_example_v1_user_proto_msgTypes[10]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *ListUsersRequest) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*ListUsersRequest) ProtoMessage() {}
+
+func (x *ListUsersRequest) ProtoReflect() protoreflect.Message {
+ mi := &file_example_v1_user_proto_msgTypes[10]
+ if x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use ListUsersRequest.ProtoReflect.Descriptor instead.
+func (*ListUsersRequest) Descriptor() ([]byte, []int) {
+ return file_example_v1_user_proto_rawDescGZIP(), []int{10}
+}
+
+func (x *ListUsersRequest) GetPageSize() int32 {
+ if x != nil {
+ return x.PageSize
+ }
+ return 0
+}
+
+func (x *ListUsersRequest) GetPageToken() string {
+ if x != nil {
+ return x.PageToken
+ }
+ return ""
+}
+
+// ListUsersResponse is the response for listing users.
+type ListUsersResponse struct {
+ state protoimpl.MessageState `protogen:"open.v1"`
+ Users []*User `protobuf:"bytes,1,rep,name=users,proto3" json:"users,omitempty"`
+ NextPageToken string `protobuf:"bytes,2,opt,name=next_page_token,json=nextPageToken,proto3" json:"next_page_token,omitempty"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *ListUsersResponse) Reset() {
+ *x = ListUsersResponse{}
+ mi := &file_example_v1_user_proto_msgTypes[11]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *ListUsersResponse) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*ListUsersResponse) ProtoMessage() {}
+
+func (x *ListUsersResponse) ProtoReflect() protoreflect.Message {
+ mi := &file_example_v1_user_proto_msgTypes[11]
+ if x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use ListUsersResponse.ProtoReflect.Descriptor instead.
+func (*ListUsersResponse) Descriptor() ([]byte, []int) {
+ return file_example_v1_user_proto_rawDescGZIP(), []int{11}
+}
+
+func (x *ListUsersResponse) GetUsers() []*User {
+ if x != nil {
+ return x.Users
+ }
+ return nil
+}
+
+func (x *ListUsersResponse) GetNextPageToken() string {
+ if x != nil {
+ return x.NextPageToken
+ }
+ return ""
+}
+
+var File_example_v1_user_proto protoreflect.FileDescriptor
+
+const file_example_v1_user_proto_rawDesc = "" +
+ "\n" +
+ "\x15example/v1/user.proto\x12\n" +
+ "example.v1\x1a google/protobuf/field_mask.proto\x1a\x1fgoogle/protobuf/timestamp.proto\x1a\x11retag/retag.proto\"\x96\x03\n" +
+ "\x04User\x12\"\n" +
+ "\x02id\x18\x01 \x01(\tB\x12\x82\xea0\x0e\n" +
+ "\x03xml\x12\aid,attrR\x02id\x12#\n" +
+ "\x04name\x18\x02 \x01(\tB\x0f\x82\xea0\v\n" +
+ "\x03xml\x12\x04nameR\x04name\x12D\n" +
+ "\x05email\x18\x03 \x01(\tB.\x82\xea0\f\n" +
+ "\x03xml\x12\x05email\x82\xea0\x1a\n" +
+ "\bvalidate\x12\x0erequired,emailR\x05email\x12*\n" +
+ "\x03age\x18\x04 \x01(\x05B\x18\x82\xea0\x14\n" +
+ "\x03xml\x12\rage,omitemptyR\x03age\x129\n" +
+ "\n" +
+ "created_at\x18\x05 \x01(\v2\x1a.google.protobuf.TimestampR\tcreatedAt\x129\n" +
+ "\n" +
+ "updated_at\x18\x06 \x01(\v2\x1a.google.protobuf.TimestampR\tupdatedAt\x12.\n" +
+ "\x06status\x18\a \x01(\x0e2\x16.example.v1.UserStatusR\x06status\x12-\n" +
+ "\aprofile\x18\b \x01(\v2\x13.example.v1.ProfileR\aprofile\"\xfa\x01\n" +
+ "\aProfile\x123\n" +
+ "\n" +
+ "avatar_url\x18\x01 \x01(\tB\x14\x82\xea0\x10\n" +
+ "\x03xml\x12\tavatarUrlR\tavatarUrl\x12*\n" +
+ "\x03bio\x18\x02 \x01(\tB\x18\x82\xea0\x14\n" +
+ "\x03xml\x12\rbio,omitemptyR\x03bio\x129\n" +
+ "\blocation\x18\x03 \x01(\tB\x1d\x82\xea0\x19\n" +
+ "\x03xml\x12\x12location,omitemptyR\blocation\x12S\n" +
+ "\awebsite\x18\x04 \x01(\tB9\x82\xea0\x18\n" +
+ "\x03xml\x12\x11website,omitempty\x82\xea0\x19\n" +
+ "\bvalidate\x12\romitempty,urlR\awebsite\"9\n" +
+ "\x11CreateUserRequest\x12$\n" +
+ "\x04user\x18\x01 \x01(\v2\x10.example.v1.UserR\x04user\":\n" +
+ "\x12CreateUserResponse\x12$\n" +
+ "\x04user\x18\x01 \x01(\v2\x10.example.v1.UserR\x04user\" \n" +
+ "\x0eGetUserRequest\x12\x0e\n" +
+ "\x02id\x18\x01 \x01(\tR\x02id\"7\n" +
+ "\x0fGetUserResponse\x12$\n" +
+ "\x04user\x18\x01 \x01(\v2\x10.example.v1.UserR\x04user\"v\n" +
+ "\x11UpdateUserRequest\x12$\n" +
+ "\x04user\x18\x01 \x01(\v2\x10.example.v1.UserR\x04user\x12;\n" +
+ "\vupdate_mask\x18\x02 \x01(\v2\x1a.google.protobuf.FieldMaskR\n" +
+ "updateMask\":\n" +
+ "\x12UpdateUserResponse\x12$\n" +
+ "\x04user\x18\x01 \x01(\v2\x10.example.v1.UserR\x04user\"#\n" +
+ "\x11DeleteUserRequest\x12\x0e\n" +
+ "\x02id\x18\x01 \x01(\tR\x02id\"\x14\n" +
+ "\x12DeleteUserResponse\"N\n" +
+ "\x10ListUsersRequest\x12\x1b\n" +
+ "\tpage_size\x18\x01 \x01(\x05R\bpageSize\x12\x1d\n" +
+ "\n" +
+ "page_token\x18\x02 \x01(\tR\tpageToken\"c\n" +
+ "\x11ListUsersResponse\x12&\n" +
+ "\x05users\x18\x01 \x03(\v2\x10.example.v1.UserR\x05users\x12&\n" +
+ "\x0fnext_page_token\x18\x02 \x01(\tR\rnextPageToken*s\n" +
+ "\n" +
+ "UserStatus\x12\x1b\n" +
+ "\x17USER_STATUS_UNSPECIFIED\x10\x00\x12\x16\n" +
+ "\x12USER_STATUS_ACTIVE\x10\x01\x12\x18\n" +
+ "\x14USER_STATUS_INACTIVE\x10\x02\x12\x16\n" +
+ "\x12USER_STATUS_BANNED\x10\x032\x82\x03\n" +
+ "\vUserService\x12K\n" +
+ "\n" +
+ "CreateUser\x12\x1d.example.v1.CreateUserRequest\x1a\x1e.example.v1.CreateUserResponse\x12B\n" +
+ "\aGetUser\x12\x1a.example.v1.GetUserRequest\x1a\x1b.example.v1.GetUserResponse\x12K\n" +
+ "\n" +
+ "UpdateUser\x12\x1d.example.v1.UpdateUserRequest\x1a\x1e.example.v1.UpdateUserResponse\x12K\n" +
+ "\n" +
+ "DeleteUser\x12\x1d.example.v1.DeleteUserRequest\x1a\x1e.example.v1.DeleteUserResponse\x12H\n" +
+ "\tListUsers\x12\x1c.example.v1.ListUsersRequest\x1a\x1d.example.v1.ListUsersResponseBH\n" +
+ "\x0ecom.example.v1P\x01Z4github.com/pubgo/protobuild/pkg/example/v1;examplev1b\x06proto3"
+
+var (
+ file_example_v1_user_proto_rawDescOnce sync.Once
+ file_example_v1_user_proto_rawDescData []byte
+)
+
+func file_example_v1_user_proto_rawDescGZIP() []byte {
+ file_example_v1_user_proto_rawDescOnce.Do(func() {
+ file_example_v1_user_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_example_v1_user_proto_rawDesc), len(file_example_v1_user_proto_rawDesc)))
+ })
+ return file_example_v1_user_proto_rawDescData
+}
+
+var file_example_v1_user_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
+var file_example_v1_user_proto_msgTypes = make([]protoimpl.MessageInfo, 12)
+var file_example_v1_user_proto_goTypes = []any{
+ (UserStatus)(0), // 0: example.v1.UserStatus
+ (*User)(nil), // 1: example.v1.User
+ (*Profile)(nil), // 2: example.v1.Profile
+ (*CreateUserRequest)(nil), // 3: example.v1.CreateUserRequest
+ (*CreateUserResponse)(nil), // 4: example.v1.CreateUserResponse
+ (*GetUserRequest)(nil), // 5: example.v1.GetUserRequest
+ (*GetUserResponse)(nil), // 6: example.v1.GetUserResponse
+ (*UpdateUserRequest)(nil), // 7: example.v1.UpdateUserRequest
+ (*UpdateUserResponse)(nil), // 8: example.v1.UpdateUserResponse
+ (*DeleteUserRequest)(nil), // 9: example.v1.DeleteUserRequest
+ (*DeleteUserResponse)(nil), // 10: example.v1.DeleteUserResponse
+ (*ListUsersRequest)(nil), // 11: example.v1.ListUsersRequest
+ (*ListUsersResponse)(nil), // 12: example.v1.ListUsersResponse
+ (*timestamppb.Timestamp)(nil), // 13: google.protobuf.Timestamp
+ (*fieldmaskpb.FieldMask)(nil), // 14: google.protobuf.FieldMask
+}
+var file_example_v1_user_proto_depIdxs = []int32{
+ 13, // 0: example.v1.User.created_at:type_name -> google.protobuf.Timestamp
+ 13, // 1: example.v1.User.updated_at:type_name -> google.protobuf.Timestamp
+ 0, // 2: example.v1.User.status:type_name -> example.v1.UserStatus
+ 2, // 3: example.v1.User.profile:type_name -> example.v1.Profile
+ 1, // 4: example.v1.CreateUserRequest.user:type_name -> example.v1.User
+ 1, // 5: example.v1.CreateUserResponse.user:type_name -> example.v1.User
+ 1, // 6: example.v1.GetUserResponse.user:type_name -> example.v1.User
+ 1, // 7: example.v1.UpdateUserRequest.user:type_name -> example.v1.User
+ 14, // 8: example.v1.UpdateUserRequest.update_mask:type_name -> google.protobuf.FieldMask
+ 1, // 9: example.v1.UpdateUserResponse.user:type_name -> example.v1.User
+ 1, // 10: example.v1.ListUsersResponse.users:type_name -> example.v1.User
+ 3, // 11: example.v1.UserService.CreateUser:input_type -> example.v1.CreateUserRequest
+ 5, // 12: example.v1.UserService.GetUser:input_type -> example.v1.GetUserRequest
+ 7, // 13: example.v1.UserService.UpdateUser:input_type -> example.v1.UpdateUserRequest
+ 9, // 14: example.v1.UserService.DeleteUser:input_type -> example.v1.DeleteUserRequest
+ 11, // 15: example.v1.UserService.ListUsers:input_type -> example.v1.ListUsersRequest
+ 4, // 16: example.v1.UserService.CreateUser:output_type -> example.v1.CreateUserResponse
+ 6, // 17: example.v1.UserService.GetUser:output_type -> example.v1.GetUserResponse
+ 8, // 18: example.v1.UserService.UpdateUser:output_type -> example.v1.UpdateUserResponse
+ 10, // 19: example.v1.UserService.DeleteUser:output_type -> example.v1.DeleteUserResponse
+ 12, // 20: example.v1.UserService.ListUsers:output_type -> example.v1.ListUsersResponse
+ 16, // [16:21] is the sub-list for method output_type
+ 11, // [11:16] is the sub-list for method input_type
+ 11, // [11:11] is the sub-list for extension type_name
+ 11, // [11:11] is the sub-list for extension extendee
+ 0, // [0:11] is the sub-list for field type_name
+}
+
+func init() { file_example_v1_user_proto_init() }
+func file_example_v1_user_proto_init() {
+ if File_example_v1_user_proto != nil {
+ return
+ }
+ type x struct{}
+ out := protoimpl.TypeBuilder{
+ File: protoimpl.DescBuilder{
+ GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
+ RawDescriptor: unsafe.Slice(unsafe.StringData(file_example_v1_user_proto_rawDesc), len(file_example_v1_user_proto_rawDesc)),
+ NumEnums: 1,
+ NumMessages: 12,
+ NumExtensions: 0,
+ NumServices: 1,
+ },
+ GoTypes: file_example_v1_user_proto_goTypes,
+ DependencyIndexes: file_example_v1_user_proto_depIdxs,
+ EnumInfos: file_example_v1_user_proto_enumTypes,
+ MessageInfos: file_example_v1_user_proto_msgTypes,
+ }.Build()
+ File_example_v1_user_proto = out.File
+ file_example_v1_user_proto_goTypes = nil
+ file_example_v1_user_proto_depIdxs = nil
+}
diff --git a/proto/example/v1/order.proto b/proto/example/v1/order.proto
index 2c6cdaa..f13fff2 100644
--- a/proto/example/v1/order.proto
+++ b/proto/example/v1/order.proto
@@ -4,7 +4,7 @@ package example.v1;
import "google/protobuf/timestamp.proto";
-option go_package = "github.com/pubgo/protobuild/proto/example/v1;examplev1";
+option go_package = "github.com/pubgo/protobuild/pkg/example/v1;examplev1";
option java_multiple_files = true;
option java_package = "com.example.v1";
diff --git a/proto/example/v1/user.proto b/proto/example/v1/user.proto
index 330023c..baa54d9 100644
--- a/proto/example/v1/user.proto
+++ b/proto/example/v1/user.proto
@@ -4,21 +4,40 @@ package example.v1;
import "google/protobuf/field_mask.proto";
import "google/protobuf/timestamp.proto";
+import "retag/retag.proto";
-option go_package = "github.com/pubgo/protobuild/proto/example/v1;examplev1";
+option go_package = "github.com/pubgo/protobuild/pkg/example/v1;examplev1";
option java_multiple_files = true;
option java_package = "com.example.v1";
// User represents a user in the system.
message User {
// Unique identifier for the user.
- string id = 1;
+ string id = 1 [(retag.tags) = {
+ name: "xml"
+ value: "id,attr"
+ }];
// User's display name.
- string name = 2;
+ string name = 2 [(retag.tags) = {
+ name: "xml"
+ value: "name"
+ }];
// User's email address.
- string email = 3;
+ string email = 3 [
+ (retag.tags) = {
+ name: "xml"
+ value: "email"
+ },
+ (retag.tags) = {
+ name: "validate"
+ value: "required,email"
+ }
+ ];
// User's age.
- int32 age = 4;
+ int32 age = 4 [(retag.tags) = {
+ name: "xml"
+ value: "age,omitempty"
+ }];
// When the user was created.
google.protobuf.Timestamp created_at = 5;
// When the user was last updated.
@@ -39,10 +58,28 @@ enum UserStatus {
// Profile contains user profile information.
message Profile {
- string avatar_url = 1;
- string bio = 2;
- string location = 3;
- string website = 4;
+ string avatar_url = 1 [(retag.tags) = {
+ name: "xml"
+ value: "avatarUrl"
+ }];
+ string bio = 2 [(retag.tags) = {
+ name: "xml"
+ value: "bio,omitempty"
+ }];
+ string location = 3 [(retag.tags) = {
+ name: "xml"
+ value: "location,omitempty"
+ }];
+ string website = 4 [
+ (retag.tags) = {
+ name: "xml"
+ value: "website,omitempty"
+ },
+ (retag.tags) = {
+ name: "validate"
+ value: "omitempty,url"
+ }
+ ];
}
// CreateUserRequest is the request for creating a user.
diff --git a/protobuf.yaml b/protobuf.yaml
index 2694587..4921e07 100644
--- a/protobuf.yaml
+++ b/protobuf.yaml
@@ -1,5 +1,9 @@
-checksum: ae1e52134aaa1770db37e83bf3cea605874942cb
+checksum: db5860630b46e9b8b0c59d190bf5639ab2da2b17
vendor: .proto
+base:
+ out: ./pkg
+ paths: import
+ module: github.com/pubgo/protobuild/pkg
root:
- proto
includes:
@@ -7,20 +11,15 @@ includes:
deps:
- name: google/protobuf
url: /usr/local/include/google/protobuf
+ - name: retag
+ url: github.com/pubgo/protoc-gen-retag
+ path: proto/retag
+ version: v0.0.5
plugins:
- name: go
- out: pkg
- opt:
- - paths=source_relative
- name: retag
- opt:
- - paths=source_relative
- - output=pkg
- - name: test
linter:
rules:
- included_paths: []
- excluded_paths: []
enabled_rules:
- core::0131::http-method
- core::0131::http-body
@@ -28,4 +27,3 @@ linter:
disabled_rules:
- all
format_type: yaml
- ignore_comment_disables_flag: false