@vafast/cli 0.1.3 → 0.1.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.
package/README.md CHANGED
@@ -32,6 +32,18 @@ npx vafast sync --url http://localhost:3000 --endpoint /api/contract
32
32
  | `--url <url>` | 服务端地址(必填) | - |
33
33
  | `--out <path>` | 输出文件路径 | `src/api.generated.ts` |
34
34
  | `--endpoint <path>` | 契约接口路径 | `/__contract__` |
35
+ | `--strip-prefix <prefix>` | 去掉路径前缀 | - |
36
+
37
+ **示例:**
38
+
39
+ ```bash
40
+ # 去掉 /restfulApi 前缀
41
+ npx vafast sync \
42
+ --url http://localhost:9002 \
43
+ --endpoint /restfulApi/api-spec \
44
+ --out src/types/api/ones.generated.ts \
45
+ --strip-prefix /restfulApi
46
+ ```
35
47
 
36
48
  ## 工作流程
37
49
 
@@ -66,13 +78,23 @@ npx vafast sync --url http://localhost:3000
66
78
  ### 3. 使用生成的类型
67
79
 
68
80
  ```typescript
69
- import { eden } from '@vafast/api-client'
70
- import type { Api } from './api.generated'
81
+ import { createClient } from '@vafast/api-client'
82
+ import { createApiClient } from './api.generated'
71
83
 
72
- const api = eden<Api>('http://localhost:3000')
84
+ // 创建底层客户端
85
+ const client = createClient({
86
+ baseURL: 'http://localhost:3000',
87
+ timeout: 30000
88
+ })
73
89
 
74
- // 类型安全的调用
90
+ // 创建类型安全的 API 客户端
91
+ const api = createApiClient(client)
92
+
93
+ // 类型安全的调用(错误路径会被 TypeScript 检测)
75
94
  const { data, error } = await api.users.get({ page: 1 })
95
+
96
+ // ❌ TypeScript 会报错
97
+ // api.nonExistent.get() // Error: Property 'nonExistent' does not exist
76
98
  ```
77
99
 
78
100
  ## 自动化
@@ -82,9 +104,12 @@ const { data, error } = await api.users.get({ page: 1 })
82
104
  ```json
83
105
  {
84
106
  "scripts": {
85
- "sync": "vafast sync --url $API_URL",
86
- "dev": "npm run sync && vite",
87
- "build": "npm run sync && vite build"
107
+ "sync:auth": "vafast sync --url http://localhost:9003 --endpoint /authRestfulApi/api-spec --out src/types/api/auth.generated.ts --strip-prefix /authRestfulApi",
108
+ "sync:ones": "vafast sync --url http://localhost:9002 --endpoint /restfulApi/api-spec --out src/types/api/ones.generated.ts --strip-prefix /restfulApi",
109
+ "sync:billing": "vafast sync --url http://localhost:9004 --endpoint /billingRestfulApi/api-spec --out src/types/api/billing.generated.ts --strip-prefix /billingRestfulApi",
110
+ "sync:types": "npm run sync:auth && npm run sync:billing && npm run sync:ones",
111
+ "dev": "vite",
112
+ "build": "npm run sync:types && vite build"
88
113
  }
89
114
  }
90
115
  ```
@@ -113,28 +138,57 @@ const { data, error } = await api.users.get({ page: 1 })
113
138
  生成的类型:
114
139
 
115
140
  ```typescript
141
+ import type { ApiResponse, RequestConfig, Client, EdenClient } from '@vafast/api-client'
142
+ import { eden } from '@vafast/api-client'
143
+
144
+ /** API 契约类型 */
116
145
  export type Api = {
117
146
  users: {
118
147
  get: {
119
148
  query: { page?: number }
120
- return: unknown
149
+ return: any
121
150
  }
122
151
  post: {
123
152
  body: { name?: string }
124
- return: unknown
153
+ return: any
125
154
  }
126
155
  }
127
156
  }
157
+
158
+ /** API 客户端类型别名 */
159
+ export type ApiClientType = EdenClient<Api>
160
+
161
+ /**
162
+ * 创建类型安全的 API 客户端
163
+ */
164
+ export function createApiClient(client: Client): EdenClient<Api> {
165
+ return eden<Api>(client)
166
+ }
167
+ ```
168
+
169
+ **使用方式:**
170
+
171
+ ```typescript
172
+ import { createClient } from '@vafast/api-client'
173
+ import { createApiClient } from './api.generated'
174
+
175
+ const client = createClient({ baseURL: '/api', timeout: 30000 })
176
+ const api = createApiClient(client)
177
+
178
+ // 完整的类型安全
179
+ const { data, error } = await api.users.post({ name: 'John' })
128
180
  ```
129
181
 
130
182
  ## 注意事项
131
183
 
132
- 1. **返回类型**:当前契约不包含返回类型信息,生成的类型中返回值为 `unknown`。如需完整类型推断,建议使用 monorepo 共享路由定义。
184
+ 1. **返回类型**:如果后端未定义 `response` schema,生成的返回类型为 `any`(渐进式类型安全)。建议后端添加 `response` schema 获得完整类型检查。
133
185
 
134
186
  2. **服务器必须运行**:执行 `sync` 命令时,服务端必须在运行并暴露契约接口。
135
187
 
136
188
  3. **不要手动修改**:生成的文件会被覆盖,请勿手动修改。
137
189
 
190
+ 4. **类型安全**:生成的 `createApiClient` 返回 `EdenClient<Api>`,TypeScript 会检测错误的 API 路径。
191
+
138
192
  ## License
139
193
 
140
194
  MIT
package/bin/vafast.js CHANGED
File without changes
package/dist/cli.js CHANGED
@@ -1,9 +1,9 @@
1
- import { syncTypes } from "./sync-Bokzgg9Z.js";
1
+ import { syncTypes } from "./sync-CS1-nDMt.js";
2
2
  import { cac } from "cac";
3
3
 
4
4
  //#region src/cli.ts
5
5
  const cli = cac("vafast");
6
- cli.command("sync", "从服务端同步 API 类型定义").option("--url <url>", "服务端地址(必填)").option("--out <path>", "输出文件路径", { default: "src/api.generated.ts" }).option("--endpoint <path>", "API Spec 接口路径", { default: "/api-spec" }).action(async (options) => {
6
+ cli.command("sync", "从服务端同步 API 类型定义").option("--url <url>", "服务端地址(必填)").option("--out <path>", "输出文件路径", { default: "src/api.generated.ts" }).option("--endpoint <path>", "API Spec 接口路径", { default: "/api-spec" }).option("--strip-prefix <prefix>", "从路径中去掉的前缀(如 /billingRestfulApi)").action(async (options) => {
7
7
  if (!options.url) {
8
8
  console.error("❌ 请指定服务端地址:--url <url>");
9
9
  process.exit(1);
@@ -11,7 +11,8 @@ cli.command("sync", "从服务端同步 API 类型定义").option("--url <url>",
11
11
  await syncTypes({
12
12
  url: options.url,
13
13
  output: options.out,
14
- endpoint: options.endpoint
14
+ endpoint: options.endpoint,
15
+ stripPrefix: options.stripPrefix
15
16
  });
16
17
  });
17
18
  cli.help();
@@ -7,6 +7,7 @@ interface SyncOptions {
7
7
  url: string;
8
8
  output: string;
9
9
  endpoint: string;
10
+ stripPrefix?: string;
10
11
  }
11
12
  /**
12
13
  * 同步 API 类型
package/dist/index.js CHANGED
@@ -1,3 +1,3 @@
1
- import { schemaToType, syncTypes } from "./sync-Bokzgg9Z.js";
1
+ import { schemaToType, syncTypes } from "./sync-CS1-nDMt.js";
2
2
 
3
3
  export { schemaToType, syncTypes };
@@ -74,7 +74,7 @@ function objectToType(schema) {
74
74
  * 同步 API 类型
75
75
  */
76
76
  async function syncTypes(options) {
77
- const { url, output, endpoint } = options;
77
+ const { url, output, endpoint, stripPrefix } = options;
78
78
  console.log(`🔄 正在从 ${url}${endpoint} 获取契约...`);
79
79
  const contractUrl = new URL(endpoint, url).toString();
80
80
  let contract;
@@ -88,7 +88,8 @@ async function syncTypes(options) {
88
88
  process.exit(1);
89
89
  }
90
90
  console.log(`✅ 获取到 ${contract.routes.length} 个路由`);
91
- const typeContent = generateTypeDefinition(contract);
91
+ if (stripPrefix) console.log(`🔧 去掉路径前缀: ${stripPrefix}`);
92
+ const typeContent = generateTypeDefinition(contract, stripPrefix);
92
93
  const outputDir = dirname(output);
93
94
  mkdirSync(outputDir, { recursive: true });
94
95
  writeFileSync(output, typeContent, "utf-8");
@@ -102,7 +103,7 @@ async function syncTypes(options) {
102
103
  /**
103
104
  * 生成类型定义文件内容
104
105
  */
105
- function generateTypeDefinition(contract) {
106
+ function generateTypeDefinition(contract, stripPrefix) {
106
107
  const lines = [];
107
108
  lines.push("/**");
108
109
  lines.push(" * 自动生成的 API 类型定义");
@@ -112,20 +113,97 @@ function generateTypeDefinition(contract) {
112
113
  lines.push(" * ⚠️ 请勿手动修改此文件,使用 `vafast sync` 重新生成");
113
114
  lines.push(" */");
114
115
  lines.push("");
115
- const routeTree = buildRouteTree(contract.routes);
116
+ lines.push("import type { ApiResponse, RequestConfig, Client, EdenClient } from '@vafast/api-client'");
117
+ lines.push("import { eden } from '@vafast/api-client'");
118
+ lines.push("");
119
+ const routeTree = buildRouteTree(contract.routes, stripPrefix);
120
+ lines.push("/** API 契约类型 */");
116
121
  lines.push("export type Api = {");
117
122
  lines.push(generateRouteTreeType(routeTree, 1));
118
123
  lines.push("}");
119
124
  lines.push("");
125
+ lines.push("/** API 客户端类型(提供完整的 IDE 智能提示) */");
126
+ lines.push("export interface ApiClient {");
127
+ lines.push(generateClientType(routeTree, 1));
128
+ lines.push("}");
129
+ lines.push("");
130
+ lines.push("/** API 客户端类型别名(基于 EdenClient 推断,提供完整类型检查) */");
131
+ lines.push("export type ApiClientType = EdenClient<Api>");
132
+ lines.push("");
133
+ lines.push("/**");
134
+ lines.push(" * 创建类型安全的 API 客户端");
135
+ lines.push(" * ");
136
+ lines.push(" * @example");
137
+ lines.push(" * ```typescript");
138
+ lines.push(" * import { createClient } from '@vafast/api-client'");
139
+ lines.push(" * import { createApiClient } from './api.generated'");
140
+ lines.push(" * ");
141
+ lines.push(" * const client = createClient('/api').use(authMiddleware)");
142
+ lines.push(" * const api = createApiClient(client)");
143
+ lines.push(" * ");
144
+ lines.push(" * // 完整的 IDE 智能提示和类型检查");
145
+ lines.push(" * const { data, error } = await api.users.find.post({ current: 1, pageSize: 10 })");
146
+ lines.push(" * // ❌ 错误路径会被 TypeScript 检测到");
147
+ lines.push(" * // api.nonExistent.post() // Error: Property 'nonExistent' does not exist");
148
+ lines.push(" * ```");
149
+ lines.push(" */");
150
+ lines.push("export function createApiClient(client: Client): EdenClient<Api> {");
151
+ lines.push(" return eden<Api>(client)");
152
+ lines.push("}");
153
+ lines.push("");
154
+ return lines.join("\n");
155
+ }
156
+ /**
157
+ * 生成客户端接口类型(带完整方法签名,IDE 友好)
158
+ */
159
+ function generateClientType(tree, indent) {
160
+ const lines = [];
161
+ const pad = " ".repeat(indent);
162
+ for (const [key, node] of tree) {
163
+ const needsQuotes = /[^a-zA-Z0-9_$]/.test(key) || /^\d/.test(key);
164
+ const propName = needsQuotes ? `'${key}'` : key;
165
+ lines.push(`${pad}${propName}: {`);
166
+ for (const [method, route] of node.methods) {
167
+ if (route.description) lines.push(`${pad} /** ${route.description} */`);
168
+ const methodSig = generateMethodSignature(route, method);
169
+ lines.push(`${pad} ${method}: ${methodSig}`);
170
+ }
171
+ if (node.children.size > 0) {
172
+ const childContent = generateClientType(node.children, indent + 1);
173
+ if (childContent) lines.push(childContent);
174
+ }
175
+ lines.push(`${pad}}`);
176
+ }
120
177
  return lines.join("\n");
121
178
  }
122
179
  /**
180
+ * 生成方法签名(函数类型)
181
+ */
182
+ function generateMethodSignature(route, method) {
183
+ const params = [];
184
+ if (route.schema?.body) {
185
+ const bodyType = schemaToType(route.schema.body);
186
+ params.push(`body: ${bodyType}`);
187
+ }
188
+ if (route.schema?.query) {
189
+ const queryType = schemaToType(route.schema.query);
190
+ params.push(`query?: ${queryType}`);
191
+ }
192
+ params.push("config?: RequestConfig");
193
+ const returnType = route.schema?.response ? schemaToType(route.schema.response) : "any";
194
+ return `(${params.join(", ")}) => Promise<ApiResponse<${returnType}>>`;
195
+ }
196
+ /**
123
197
  * 构建路由树
124
198
  */
125
- function buildRouteTree(routes) {
199
+ function buildRouteTree(routes, stripPrefix) {
126
200
  const root = /* @__PURE__ */ new Map();
201
+ const normalizedPrefix = stripPrefix ? "/" + stripPrefix.replace(/^\/+|\/+$/g, "") : void 0;
127
202
  for (const route of routes) {
128
- const segments = route.path.split("/").filter(Boolean);
203
+ let path = route.path;
204
+ if (normalizedPrefix && path.startsWith(normalizedPrefix)) path = path.slice(normalizedPrefix.length) || "/";
205
+ const segments = path.split("/").filter(Boolean);
206
+ if (segments.length === 0) continue;
129
207
  let current = root;
130
208
  for (let i = 0; i < segments.length; i++) {
131
209
  const segment = segments[i];
@@ -183,7 +261,10 @@ function generateMethodType(route) {
183
261
  const paramsType = schemaToType(route.schema.params);
184
262
  parts.push(`params: ${paramsType}`);
185
263
  }
186
- parts.push("return: unknown");
264
+ if (route.schema?.response) {
265
+ const responseType = schemaToType(route.schema.response);
266
+ parts.push(`return: ${responseType}`);
267
+ } else parts.push("return: any");
187
268
  if (parts.length === 1) return `{ ${parts[0]} }`;
188
269
  return `{\n ${parts.join("\n ")}\n }`;
189
270
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vafast/cli",
3
- "version": "0.1.3",
3
+ "version": "0.1.5",
4
4
  "description": "Vafast CLI - 类型同步和开发工具",
5
5
  "type": "module",
6
6
  "bin": {