ts-proto-client 1.0.3 → 1.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +232 -217
  2. package/dist/index.js +4 -4
  3. package/package.json +4 -5
package/README.md CHANGED
@@ -1,217 +1,232 @@
1
- # ts-proto-client
2
-
3
- 基于 Proto 文件自动生成 TypeScript 类型和 API 请求代码的 CLI 工具。
4
-
5
- ## 功能
6
-
7
- * 根据 `.proto` 文件生成 TypeScript 类型
8
- * 根据 Service 定义生成 API 请求方法
9
- * 支持自定义 Proto 根目录
10
- * 支持自定义类型输出目录
11
- * 支持自定义 API 输出目录
12
-
13
- ---
14
-
15
- ## 安装前准备
16
-
17
- 本工具依赖 `protoc`,请先安装 Protocol Buffers 编译器。
18
-
19
- ### macOS
20
-
21
- 安装:
22
-
23
- ```bash
24
- brew install protobuf
25
- ```
26
-
27
- 验证:
28
-
29
- ```bash
30
- protoc --version
31
- ```
32
-
33
- 输出示例:
34
-
35
- ```text
36
- libprotoc 35.1
37
- ```
38
-
39
- ---
40
-
41
- ### Windows
42
-
43
- 1. 打开 Protocol Buffers Releases 页面
44
-
45
- https://github.com/protocolbuffers/protobuf/releases
46
-
47
- 2. 下载对应版本
48
-
49
- ```text
50
- protoc-35.1-win64.zip
51
- ```
52
-
53
- 3. 解压到目录,例如:
54
-
55
- ```text
56
- D:\protoc
57
- ```
58
-
59
- 目录结构:
60
-
61
- ```text
62
- D:\protoc
63
- ├─ bin
64
- │ └─ protoc.exe
65
- ├─ include
66
- └─ readme.txt
67
- ```
68
-
69
- 4. 将以下目录添加到系统环境变量:
70
-
71
- ```text
72
- D:\protoc\bin
73
- ```
74
-
75
- 5. 打开新的终端验证:
76
-
77
- ```bash
78
- protoc --version
79
- ```
80
-
81
- 输出示例:
82
-
83
- ```text
84
- libprotoc 35.1
85
- ```
86
-
87
- ---
88
-
89
- ## 使用方式
90
-
91
- ### 生成代码
92
-
93
- ```bash
94
- npx proto-gen generate \
95
- --file ./account.proto \
96
- --root ../protos \
97
- --type-output src/protos/type \
98
- --api-output src/protos/api/account
99
- ```
100
-
101
- ### 参数说明
102
-
103
- | 参数 | 必填 | 说明 |
104
- | --------------- | -- | ------------------------- |
105
- | `--file` | 是 | Proto 文件路径(相对于 Proto 根目录) |
106
- | `--root` | 是 | Proto 根目录 |
107
- | `--type-output` | 是 | TypeScript 类型输出目录 |
108
- | `--api-output` | 是 | API 文件输出目录 |
109
-
110
- ---
111
-
112
- ## 示例
113
-
114
- 目录结构:
115
-
116
- ```text
117
- project
118
- ├─ src
119
- ├─ protos
120
- │ ├─ hospital
121
- │ │ └─ account.proto
122
- │ └─ third_party
123
- └─ package.json
124
- ```
125
-
126
- 执行:
127
-
128
- ```bash
129
- npx proto-gen generate \
130
- --file hospital/account.proto \
131
- --root ./protos \
132
- --type-output src/protos/type \
133
- --api-output src/protos/api/account
134
- ```
135
-
136
- 生成:
137
-
138
- ```text
139
- src
140
- └─ protos
141
- ├─ type
142
- │ └─ account.ts
143
- └─ api
144
- └─ account.ts
145
- ```
146
-
147
- ---
148
-
149
- ## 本地开发
150
-
151
- 启动开发模式:
152
-
153
- ```bash
154
- pnpm dev generate \
155
- --file hospital/account.proto \
156
- --root ../protos \
157
- --type-output src/protos/type \
158
- --api-output src/protos/api/account
159
- ```
160
-
161
- ---
162
-
163
- ## 常见问题
164
-
165
- ### protoc: command not found
166
-
167
- 请确认已经正确安装 protoc,并配置到环境变量:
168
-
169
- ```bash
170
- protoc --version
171
- ```
172
-
173
- 能够正常输出版本号。
174
-
175
- ---
176
-
177
- ### File does not reside within any path specified using --proto_path
178
-
179
- 请检查:
180
-
181
- ```bash
182
- --file
183
- ```
184
-
185
- 指定的文件是否位于:
186
-
187
- ```bash
188
- --root
189
- ```
190
-
191
- 指定的目录之下。
192
-
193
- 例如:
194
-
195
- ```bash
196
- --root ./protos
197
- --file hospital/account.proto
198
- ```
199
-
200
- 则实际文件应存在:
201
-
202
- ```text
203
- ./protos/hospital/account.proto
204
- ```
205
-
206
- ---
207
-
208
- ## 环境要求
209
-
210
- * Node.js >= 18
211
- * protoc >= 35
212
-
213
- ---
214
-
215
- ## License
216
-
217
- MIT
1
+ # ts-proto-client
2
+
3
+ 基于 Proto 文件自动生成 TypeScript 类型声明和 API 请求函数的 CLI 工具。
4
+
5
+ ## 功能
6
+
7
+ - **类型生成** — 调用 `protoc` + `ts-proto` 插件,将 `.proto` 文件编译为 TypeScript 类型声明
8
+ - **API 生成** 解析 Proto 中的 `service` 定义及 HTTP 注解(`google.api.http`),自动生成类型安全的 API 请求函数
9
+ - **自动识别 Proto 根目录** — 从文件路径中自动定位 `protos` 目录,无需手动配置
10
+ - **灵活的输出路径** — 支持自定义 TypeScript 类型和 API 代码的输出目录
11
+
12
+ ---
13
+
14
+ ## 原理
15
+
16
+ 工具将 `.proto` 文件拆分为两个产物:
17
+
18
+ | 产物 | 生成方式 | 说明 |
19
+ | --- | --- | --- |
20
+ | **TypeScript 类型** (`.ts`) | 调用 `protoc` + `ts-proto` 插件 | 将 proto message 编译为 TS interface/type |
21
+ | **API 请求函数** (`.ts`) | 使用 `protobufjs` 解析 proto,提取 `service` + HTTP 注解 | 生成调用 `service()` 的函数,入参与返回值均有类型约束 |
22
+
23
+ 生成的 API 函数依赖项目中的 `service` 工具函数(需由使用者自行实现),函数签名示例:
24
+
25
+ ```ts
26
+ import { service } from "../..";
27
+
28
+ import type {
29
+ HelloRequest,
30
+ HelloResponse,
31
+ } from "@/protos/type/hello";
32
+
33
+ // 登录
34
+ export function Login(data: HelloRequest) {
35
+ return service<HelloResponse["data"]>({
36
+ url: "/hello",
37
+ method: "post",
38
+ data
39
+ })
40
+ }
41
+ ```
42
+
43
+ ---
44
+
45
+ ## 环境要求
46
+
47
+ - **Node.js** >= 18
48
+ - **protoc** >= 3.15(需支持 `--ts_proto_out`)
49
+ - **pnpm**(推荐,也可使用 npm / yarn)
50
+
51
+ ---
52
+
53
+ ## 安装
54
+
55
+ ```bash
56
+ # 安装本工具
57
+ pnpm add -D ts-proto-client
58
+
59
+ # 安装 ts-proto 依赖(本工具通过 protoc 调用 ts-proto 插件)
60
+ pnpm add -D ts-proto
61
+ ```
62
+
63
+ ---
64
+
65
+ ## protoc 安装
66
+
67
+ 本工具依赖 `protoc`(Protocol Buffers 编译器),请先安装。
68
+
69
+ ### macOS
70
+
71
+ ```bash
72
+ brew install protobuf
73
+ protoc --version # 验证安装
74
+ ```
75
+
76
+ ### Windows
77
+
78
+ 1. 从 [Protocol Buffers Releases](https://github.com/protocolbuffers/protobuf/releases) 下载 `protoc-<version>-win64.zip`
79
+ 2. 解压到目标目录(如 `D:\protoc`)
80
+ 3. 将 `D:\protoc\bin` 添加到系统环境变量 `PATH`
81
+ 4. 验证:`protoc --version`
82
+
83
+ ---
84
+
85
+ ## 使用方式
86
+
87
+ ```bash
88
+ # 基本用法
89
+ npx proto-gen generate --file <proto 文件路径>
90
+
91
+ # 自定义输出路径
92
+ npx proto-gen generate \
93
+ --file <proto 文件路径> \
94
+ --type-path <类型输出目录> \
95
+ --api-path <API 输出目录>
96
+ ```
97
+
98
+ ### 示例
99
+
100
+ ```bash
101
+ npx proto-gen generate \
102
+ --file ./src/data/protos/xishan-guiyuan/ruralVisualization.proto
103
+ ```
104
+
105
+ ---
106
+
107
+ ## 参数说明
108
+
109
+ | 参数 | 必填 | 默认值 | 说明 |
110
+ | --- | --- | --- | --- |
111
+ | `--file` | 是 | — | Proto 文件的完整路径 |
112
+ | `--type-path` | 否 | `src/protos/type` | TypeScript 类型声明的输出目录 |
113
+ | `--api-path` | 否 | `src/protos/api` | API 请求函数的输出目录 |
114
+
115
+ ---
116
+
117
+ ## 目录约定
118
+
119
+ 工具会在 `--file` 的文件路径中自动查找 **`protos`** 目录,并将其作为 Proto 根目录。
120
+
121
+ ### 示例目录结构
122
+
123
+ ```text
124
+ src
125
+ └─ data
126
+ └─ protos ← 工具自动识别此目录为 Proto Root
127
+ ├─ hospital
128
+ │ └─ account.proto
129
+ └─ xishan-guiyuan
130
+ └─ ruralVisualization.proto
131
+ ```
132
+
133
+ ### 生成结果
134
+
135
+ 执行:
136
+
137
+ ```bash
138
+ npx proto-gen generate \
139
+ --file ./src/data/protos/xishan-guiyuan/ruralVisualization.proto
140
+ ```
141
+
142
+ 输出:
143
+
144
+ ```text
145
+ src
146
+ └─ protos
147
+ ├─ type ← TypeScript 类型(默认)
148
+ │ └─ xishan-guiyuan
149
+ │ └─ ruralVisualization.ts
150
+ └─ api ← API 请求函数(默认)
151
+ └─ ruralVisualization.ts
152
+ ```
153
+
154
+ 生成的目录结构会保持 proto 文件在 `protos` 下的相对路径。
155
+
156
+ ---
157
+
158
+ ## Proto 文件要求
159
+
160
+ 工具依赖 Proto 文件中的 **HTTP 注解**来生成 API 函数,需要你的 `.proto` 文件包含:
161
+
162
+ 1. **`google.api.http` 选项** — 定义 HTTP 方法和路径
163
+ 2. **`openapiv2_operation.summary` 选项** — 作为生成函数的注释(可选)
164
+
165
+ 示例:
166
+
167
+ ```protobuf
168
+ import "google/api/annotations.proto";
169
+
170
+ service UserService {
171
+ rpc GetUser(GetUserRequest) returns (GetUserResponse) {
172
+ option (google.api.http) = {
173
+ get: "/api/user/{id}"
174
+ };
175
+ option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
176
+ summary: "获取用户信息"
177
+ };
178
+ }
179
+ }
180
+ ```
181
+
182
+ > 需要第三方 proto 文件(`google/api/annotations.proto` 等),请将它们放置在 `protos/third_party/googleapis/` 目录下。
183
+
184
+ ---
185
+
186
+ ## 本地开发
187
+
188
+ ```bash
189
+ # 开发模式(直接运行 TS 源码)
190
+ pnpm dev generate --file <proto 文件路径>
191
+
192
+ # 构建
193
+ pnpm build
194
+ ```
195
+
196
+ ---
197
+
198
+ ## 常见问题
199
+
200
+ ### protoc: command not found
201
+
202
+ `protoc` 未安装或未添加到 PATH,请参考上方 [protoc 安装](#protoc-安装) 章节。
203
+
204
+ ### protoc-gen-ts_proto: command not found
205
+
206
+ `ts-proto` 未安装,执行:
207
+
208
+ ```bash
209
+ pnpm add -D ts-proto
210
+ ```
211
+
212
+ ### 未找到 protos 根目录
213
+
214
+ 工具通过查找路径中的 `protos` 目录来确定 Proto 根目录。如果你的目录名不是 `protos`(例如 `protobuf`),将无法自动识别。
215
+
216
+ 正确示例:
217
+
218
+ ```bash
219
+ --file ./src/data/protos/hospital/account.proto ✅
220
+ ```
221
+
222
+ 无法识别:
223
+
224
+ ```bash
225
+ --file ./src/data/protobuf/hospital/account.proto ❌
226
+ ```
227
+
228
+ ---
229
+
230
+ ## License
231
+
232
+ MIT
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
- import{Command as q}from"commander";import R from"fs";import b from"path";import y from"fs";import j from"path";import{Root as x,Service as P}from"protobufjs";import u from"path";import C from"fs";var g=["get","post","put","delete","patch"];function w(t){let o=[t,u.join(t,"/third_party/googleapis")],e=new x;return e.resolvePath=(n,p)=>{for(let s of o){let i=u.join(s,p);if(C.existsSync(i))return i}return p},e}function h(t,o=[]){if(!t.nested)return o;for(let e of Object.values(t.nested))e instanceof P&&o.push(e),h(e,o);return o}function A(t){let o=t.parsedOptions?.find(n=>n["(google.api.http)"])?.["(google.api.http)"];if(!o)return null;let e=g.find(n=>o[n]);return e?{method:e,url:o[e],summary:t.options?.["(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation).summary"]}:null}function O(t,o){let e=u.resolve(process.cwd(),t),n=w(e);n.loadSync(u.join(e,o));let p=h(n),s={};for(let i of p){let a=[];for(let r of Object.values(i.methods)){let m=A(r);m&&a.push({name:r.name,requestType:r.requestType,responseType:r.responseType,...m})}s[i.name]=a}return s}function v(t,o){let e=[];for(let[n,p]of Object.entries(t)){let s=new Set,i=[];for(let r of p){s.add(r.requestType),s.add(r.responseType);let m=r.method==="get"?"params: data":"data",d=[];r.summary&&d.push(`// ${r.summary}`),d.push(`export function ${r.name}(data: ${r.requestType}) {`,` return service<${r.responseType}>({`,` url: "${r.url.replace("/api","")}",`,` method: "${r.method}",`,` ${m}`," })","}"),i.push(d.join(`
3
- `))}let a=[...s].sort();e.push({[n]:['import { service } from "@/plugins";',"","import type {",` ${a.join(`,
4
- `)}`,`} from "${o}";`,"",i.join(`
2
+ import{Command as M}from"commander";import k from"fs";import R from"path";import E from"fs";import l from"fs";import j from"path";import{Root as D,Service as H}from"protobufjs";import a from"path";import z from"fs";var g=["get","post","put","delete","patch"],h="(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation).summary",d="(google.api.http)";function T(t){let r=a.normalize(t).split(a.sep).indexOf("protos");if(r===-1)throw new Error("\u672A\u627E\u5230 protos \u6839\u76EE\u5F55");return r}function I(t){let o=[t,a.join(t,"third_party/googleapis")],e=new D;return e.resolvePath=(r,p)=>{for(let n of o){let s=a.join(n,p);if(z.existsSync(s))return s}return p},e}function v(t,o=[]){if(!t.nested)return o;for(let e of Object.values(t.nested))e instanceof H&&o.push(e),v(e,o);return o}function b(t){let e=t.parsedOptions?.find(n=>n[d])?.[d];if(!e)return null;let r=g.find(n=>e[n]);if(!r)return null;let p=e[r];return p?{method:r,url:p,summary:t.options?.[h]??""}:null}function O(t){let o=I(t.root);o.loadSync(t.file);let e=v(o),r={};for(let p of e){let n=[];for(let s of Object.values(p.methods)){let f=b(s);f&&n.push({name:s.name,requestType:s.requestType,responseType:s.responseType,hasData:!!s.resolvedResponseType?.fields.data,...f})}n.length>0&&(r[p.name]=n)}return r}function x(t,o){let e={};for(let[r,p]of Object.entries(t)){let n=new Set,s=[];for(let i of p){n.add(i.requestType),i.hasData&&n.add(i.responseType);let u=[],w=i.method==="get"?"params: data":"data";i.summary&&u.push(`// ${i.summary}`);let A=i.hasData?`<${i.responseType}["data"]>`:"";u.push(`export function ${i.name}(data: ${i.requestType}) {`,` return service${A}({`,` url: "${i.url.replace("/api","")}",`,` method: "${i.method}",`,` ${w}`," })","}"),s.push(u.join(`
3
+ `))}if(s.length===0)continue;let f=[...n].sort();e[r]=['import { service } from "../..";',"","import type {",` ${f.join(`,
4
+ `)}`,`} from "${o}";`,"",s.join(`
5
5
 
6
6
  `)].join(`
7
- `)})}return e}function T(t,o){let e=o.replace(/\\/g,"/").replace(/^\.?\//,"").replace(/\.proto$/,"");return u.join(t,e)}function _(t){let o=j.resolve(process.cwd(),t.apiOutput);y.existsSync(o)||y.mkdirSync(o,{recursive:!0});let e=O(t.root,t.file),n=T(t.typeOutput,t.file),p=j.relative(t.apiOutput,n).replace(/\\/g,"/"),s=v(e,p);for(let i of s)for(let[a,r]of Object.entries(i))y.writeFileSync(`${o}/${a}.ts`,r,"utf8");console.log("\u2705 Generate Success")}import{execSync as H}from"child_process";import f from"fs";import c from"path";function S(t){let o=c.resolve(process.cwd(),t.typeOutput);f.existsSync(o)||f.mkdirSync(o,{recursive:!0});let e=c.resolve(process.cwd(),t.root),n=["protoc",`--ts_proto_out=${o}`,"--ts_proto_opt=onlyTypes=true","--ts_proto_opt=useOptionals=messages","--ts_proto_opt=esModuleInterop=true",`--proto_path=${e}`,`--proto_path=${c.join(e,"/third_party/googleapis")}`,c.join(e,t.file)],p={...process.env,PATH:`${c.join(process.cwd(),"node_modules",".bin")};${process.env.PATH}`};H(n.join(" "),{stdio:"inherit",env:p}),f.rmSync(c.join(o,"google"),{recursive:!0,force:!0}),f.rmSync(c.join(o,"common.ts"),{force:!0})}async function $(t){if(!R.existsSync(b.resolve(process.cwd(),t.root))){console.log(`proto \u9879\u76EE\u4E0D\u5B58\u5728(${t.root})`);return}S(t),_(t)}var l=new q;l.name("proto-gen").description("proto code generator").version("1.0.0");l.command("generate").option("--file, --file <path>").option("--root, --root <path>").option("--type-output, --typeOutput <path>").option("--api-output, --apiOutput <path>").action(t=>{if(t.file===void 0){console.log("proto file \u4E0D\u80FD\u4E3A\u7A7A, \u8BF7\u8F93\u5165 --file ./account.proto");return}if(t.root===void 0){console.log("proto \u6587\u4EF6\u7684\u6839\u8DEF\u5F84\u4E0D\u80FD\u4E3A\u7A7A, \u8BF7\u8F93\u5165 --root ../protos");return}t.typeOutput===void 0&&(t.typeOutput="src/protos/type"),t.apiOutput===void 0&&(t.apiOutput="src/protos/api"),$(t)});l.parse();
7
+ `)}return e}function P(t){let e=a.normalize(t).split(a.sep),r=T(t);return e.slice(0,r+1).join(a.sep)}function S(t){let e=a.normalize(t.file).split(a.sep),r=T(t.file),p=e.slice(r+1).join(a.sep),n=a.join(t.typeOutput,p.replace(/\.proto$/,""));return a.relative(t.apiOutput,n).replace(/\\/g,"/")}function _(t){let o=j.resolve(process.cwd(),t.apiOutput);l.existsSync(o)||l.mkdirSync(o,{recursive:!0});let e=O(t),r=S(t),p=x(e,r);for(let[n,s]of Object.entries(p))l.writeFileSync(j.join(o,`${n}.ts`),s,"utf8");console.log("\u2705 Generate Success")}import{execSync as q}from"child_process";import m from"fs";import c from"path";function $(t){let o=c.resolve(process.cwd(),"node_modules/ts-proto/package.json");if(!m.existsSync(o))throw new Error("\u672A\u5B89\u88C5 ts-proto\uFF0C\u8BF7\u6267\u884C\uFF1Apnpm add -D ts-proto");let e=c.resolve(process.cwd(),t.typeOutput);m.existsSync(e)||m.mkdirSync(e,{recursive:!0});let r=["protoc",`--ts_proto_out=${e}`,"--ts_proto_opt=onlyTypes=true","--ts_proto_opt=useOptionals=optional","--ts_proto_opt=esModuleInterop=true",`--proto_path=${t.root}`,`--proto_path=${c.join(t.root,"third_party/googleapis")}`,c.resolve(t.file)];q(r.join(" "),{stdio:"inherit"}),m.rmSync(c.join(e,"google"),{recursive:!0,force:!0}),m.rmSync(c.join(e,"common.ts"),{force:!0})}function C(t){if(!E.existsSync(t.root)){console.log(`proto \u9879\u76EE\u4E0D\u5B58\u5728(${t.root})`);return}$(t),_(t)}var y=new M;y.name("proto-gen").description("proto code generator").version("1.0.4");y.command("generate").requiredOption("--file <path>","proto \u6587\u4EF6\u8DEF\u5F84").option("--api-path <path>","API \u4EE3\u7801\u8F93\u51FA\u76EE\u5F55").option("--type-path <path>","TypeScript \u7C7B\u578B\u8F93\u51FA\u76EE\u5F55").action(t=>{if(!k.existsSync(t.file)){console.log(`\u274C ${t.file} \u6587\u4EF6\u4E0D\u5B58\u5728\uFF0C\u8BF7\u68C0\u67E5\u6587\u4EF6\u8DEF\u5F84`);return}let o=R.parse(t.file).name,e=P(t.file),r={file:t.file,root:R.resolve(process.cwd(),e),typeOutput:t.typePath||"src/protos/type",apiOutput:t.apiPath?`${t.apiPath}${o?"/"+o:""}`:`src/protos/api${o?"/"+o:""}`};C(r)});y.parse();
package/package.json CHANGED
@@ -1,12 +1,11 @@
1
1
  {
2
2
  "name": "ts-proto-client",
3
- "version": "1.0.3",
3
+ "version": "1.0.5",
4
4
  "type": "module",
5
5
  "description": "",
6
6
  "scripts": {
7
7
  "dev": "tsx src/index.ts",
8
- "build": "tsup src/index.ts --format esm --clean",
9
- "release": "pnpm build && npm publish"
8
+ "build": "tsup src/index.ts --format esm --clean"
10
9
  },
11
10
  "bin": {
12
11
  "proto-gen": "./dist/index.js"
@@ -20,11 +19,11 @@
20
19
  "packageManager": "pnpm@10.14.0",
21
20
  "dependencies": {
22
21
  "commander": "^15.0.0",
23
- "protobufjs": "^8.6.3",
24
- "ts-proto": "^2.11.8"
22
+ "protobufjs": "^8.6.3"
25
23
  },
26
24
  "devDependencies": {
27
25
  "@types/node": "^25.9.3",
26
+ "ts-proto": "^2.11.8",
28
27
  "tsup": "^8.5.1",
29
28
  "tsx": "^4.22.4",
30
29
  "typescript": "^6.0.3"