vite-plugin-swagger-typescript-api-transform 0.0.1 → 0.0.2
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/dist/astro.d.ts +1 -1
- package/dist/astro.js +2 -2
- package/dist/esbuild.d.ts +3 -3
- package/dist/esbuild.js +2 -2
- package/dist/farm.d.ts +3 -3
- package/dist/farm.js +2 -2
- package/dist/index.d.ts +1 -1
- package/dist/index.js +2 -2
- package/dist/nuxt.d.ts +3 -3
- package/dist/nuxt.js +4 -4
- package/dist/rollup.d.ts +3 -3
- package/dist/rollup.js +2 -2
- package/dist/rspack.d.ts +1 -1
- package/dist/rspack.js +2 -2
- package/dist/src-Cs3STwE3.js +687 -0
- package/dist/types.d.ts +1 -1
- package/dist/utils.js +1 -1
- package/dist/{vite-BKknryyS.js → vite-7Ak6OVE_.js} +1 -1
- package/dist/vite.d.ts +3 -3
- package/dist/vite.js +3 -3
- package/dist/{webpack-9obP3bLC.js → webpack-DmPwS49J.js} +1 -1
- package/dist/webpack.d.ts +3 -3
- package/dist/webpack.js +3 -3
- package/package.json +2 -2
- package/dist/prompt-CQ_38Ofh.js +0 -852
- package/dist/src-DPpaOQvV.js +0 -197001
- /package/dist/{types-CpgPraRT.d.ts → types-BJUz_-Xc.d.ts} +0 -0
- /package/dist/{utils-BypLbD1p.js → utils-DGqLkcWK.js} +0 -0
|
@@ -0,0 +1,687 @@
|
|
|
1
|
+
import { ensureDir, findYamlDirectories, findYamlFiles, formatModuleName } from "./utils-DGqLkcWK.js";
|
|
2
|
+
import { createUnplugin } from "unplugin";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import process from "node:process";
|
|
5
|
+
import fs from "node:fs";
|
|
6
|
+
import { generateApi } from "swagger-typescript-api";
|
|
7
|
+
|
|
8
|
+
//#region src/core/constants.ts
|
|
9
|
+
const PLUGIN_NAME = "swagger-typescript-api";
|
|
10
|
+
const DEFAULT_OPTIONS = {
|
|
11
|
+
outputDir: "src/api",
|
|
12
|
+
generateClient: true,
|
|
13
|
+
httpClient: "fetch",
|
|
14
|
+
watch: true,
|
|
15
|
+
moduleNameFormat: "kebab"
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
//#endregion
|
|
19
|
+
//#region src/core/generator.ts
|
|
20
|
+
/**
|
|
21
|
+
* 根据单个 YAML 文件生成 TypeScript API 代码
|
|
22
|
+
*/
|
|
23
|
+
async function generateTypeScriptFromYaml(yamlPath, outputDir, options) {
|
|
24
|
+
const fileName = path.basename(yamlPath, path.extname(yamlPath));
|
|
25
|
+
const moduleName = formatModuleName(fileName, options.moduleNameFormat);
|
|
26
|
+
const apiClassName = options.apiClassName || `${formatModuleName(fileName, "pascal")}Api`;
|
|
27
|
+
const moduleOutputDir = path.join(outputDir, moduleName);
|
|
28
|
+
ensureDir(moduleOutputDir);
|
|
29
|
+
console.log(`[${PLUGIN_NAME}] 正在生成 API: ${fileName} -> ${moduleName}`);
|
|
30
|
+
try {
|
|
31
|
+
const result = await generateApi({
|
|
32
|
+
input: yamlPath,
|
|
33
|
+
httpClientType: options.httpClient,
|
|
34
|
+
generateClient: options.generateClient,
|
|
35
|
+
generateRouteTypes: true,
|
|
36
|
+
generateResponses: true,
|
|
37
|
+
defaultResponseAsSuccess: false,
|
|
38
|
+
toJS: false,
|
|
39
|
+
extractRequestParams: true,
|
|
40
|
+
extractRequestBody: true,
|
|
41
|
+
extractEnums: true,
|
|
42
|
+
singleHttpClient: true,
|
|
43
|
+
modular: false,
|
|
44
|
+
unwrapResponseData: true,
|
|
45
|
+
apiClassName,
|
|
46
|
+
silent: true
|
|
47
|
+
});
|
|
48
|
+
const files = result.files;
|
|
49
|
+
for (const file of files) {
|
|
50
|
+
const fullFileName = `${file.fileName}${file.fileExtension}`;
|
|
51
|
+
const filePath = path.join(moduleOutputDir, fullFileName);
|
|
52
|
+
fs.writeFileSync(filePath, file.fileContent, "utf-8");
|
|
53
|
+
console.log(`[${PLUGIN_NAME}] 生成文件: ${fullFileName}`);
|
|
54
|
+
}
|
|
55
|
+
return {
|
|
56
|
+
moduleName,
|
|
57
|
+
apiClassName
|
|
58
|
+
};
|
|
59
|
+
} catch (error) {
|
|
60
|
+
console.error(`[${PLUGIN_NAME}] 生成 API 失败: ${yamlPath}`);
|
|
61
|
+
console.error(error);
|
|
62
|
+
throw error;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
//#endregion
|
|
67
|
+
//#region src/generators/example.ts
|
|
68
|
+
/**
|
|
69
|
+
* 为每个模块生成示例文件
|
|
70
|
+
*/
|
|
71
|
+
function generateModuleExampleFile(moduleOutputDir, moduleName, apiClassName) {
|
|
72
|
+
const instanceName = moduleName.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
|
|
73
|
+
const exampleContent = `/**
|
|
74
|
+
* ${moduleName} API 使用示例
|
|
75
|
+
* 由 vite-plugin-swagger-typescript-api 自动生成
|
|
76
|
+
*
|
|
77
|
+
* 注意:此文件仅为示例,可以根据实际需求修改或删除
|
|
78
|
+
*
|
|
79
|
+
* @常见问题 - CORS 跨域请求失败
|
|
80
|
+
* 如果在 Network 面板看到 OPTIONS 请求(preflight)失败,这是因为跨域请求被浏览器拦截。
|
|
81
|
+
*
|
|
82
|
+
* 解决方案:使用 Vite 代理(推荐开发环境)
|
|
83
|
+
*
|
|
84
|
+
* 1. 在 vite.config.ts 中配置代理:
|
|
85
|
+
* \`\`\`typescript
|
|
86
|
+
* export default defineConfig({
|
|
87
|
+
* server: {
|
|
88
|
+
* proxy: {
|
|
89
|
+
* '/api': {
|
|
90
|
+
* target: 'https://your-api.example.com', // 替换为实际 API 地址
|
|
91
|
+
* changeOrigin: true,
|
|
92
|
+
* }
|
|
93
|
+
* }
|
|
94
|
+
* }
|
|
95
|
+
* })
|
|
96
|
+
* \`\`\`
|
|
97
|
+
*
|
|
98
|
+
* 2. 将 baseUrl 改为相对路径:
|
|
99
|
+
* \`\`\`typescript
|
|
100
|
+
* const httpClient = new HttpClient({
|
|
101
|
+
* baseUrl: '/api', // 走 Vite 代理,避免跨域
|
|
102
|
+
* baseApiParams: {
|
|
103
|
+
* headers: { Authorization: 'Bearer your-token' },
|
|
104
|
+
* }
|
|
105
|
+
* })
|
|
106
|
+
* \`\`\`
|
|
107
|
+
*/
|
|
108
|
+
|
|
109
|
+
import { ${apiClassName}, HttpClient } from '.'
|
|
110
|
+
|
|
111
|
+
// ============================================
|
|
112
|
+
// 示例 1: 使用预配置的 API 实例(推荐)
|
|
113
|
+
// ============================================
|
|
114
|
+
// 从统一入口导入预配置的实例
|
|
115
|
+
import { ${instanceName}Api } from '..'
|
|
116
|
+
|
|
117
|
+
async function example1() {
|
|
118
|
+
try {
|
|
119
|
+
// 直接使用预配置的 API 实例
|
|
120
|
+
// const result = await ${instanceName}Api.xxx.xxxMethod({ ... })
|
|
121
|
+
console.log('使用预配置实例发起请求')
|
|
122
|
+
// return result
|
|
123
|
+
}
|
|
124
|
+
catch (error) {
|
|
125
|
+
console.error('请求失败:', error)
|
|
126
|
+
throw error
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// ============================================
|
|
131
|
+
// 示例 2: 使用自定义配置的 HTTP 客户端
|
|
132
|
+
// ============================================
|
|
133
|
+
|
|
134
|
+
async function example2() {
|
|
135
|
+
// 创建自定义配置的 HTTP 客户端
|
|
136
|
+
const httpClient = new HttpClient({
|
|
137
|
+
baseUrl: 'https://api.example.com/v1',
|
|
138
|
+
baseApiParams: {
|
|
139
|
+
headers: {
|
|
140
|
+
Authorization: 'Bearer your-token-here',
|
|
141
|
+
},
|
|
142
|
+
},
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
// 使用自定义客户端创建 API 实例
|
|
146
|
+
const api = new ${apiClassName}(httpClient)
|
|
147
|
+
|
|
148
|
+
try {
|
|
149
|
+
// const result = await api.xxx.xxxMethod({ ... })
|
|
150
|
+
console.log('使用自定义配置发起请求')
|
|
151
|
+
// return result
|
|
152
|
+
}
|
|
153
|
+
catch (error) {
|
|
154
|
+
console.error('请求失败:', error)
|
|
155
|
+
throw error
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// ============================================
|
|
160
|
+
// 示例 3: 带错误处理的请求封装
|
|
161
|
+
// ============================================
|
|
162
|
+
|
|
163
|
+
async function safeRequest<T>(
|
|
164
|
+
request: () => Promise<T>,
|
|
165
|
+
): Promise<{ data?: T; error?: Error }> {
|
|
166
|
+
try {
|
|
167
|
+
const data = await request()
|
|
168
|
+
return { data }
|
|
169
|
+
}
|
|
170
|
+
catch (error) {
|
|
171
|
+
return { error: error as Error }
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
176
|
+
async function example3() {
|
|
177
|
+
// 替换 xxx 为实际的 API 分组名称(如 users、products 等)
|
|
178
|
+
// 替换 xxxMethod 为实际的方法名称(如 usersCreate、productsList 等)
|
|
179
|
+
// const { data, error } = await safeRequest(() =>
|
|
180
|
+
// ${instanceName}Api.xxx.xxxMethod({ /* params */ }),
|
|
181
|
+
// )
|
|
182
|
+
const { data, error } = { data: undefined, error: undefined }
|
|
183
|
+
|
|
184
|
+
if (error) {
|
|
185
|
+
console.error('请求失败:', error.message)
|
|
186
|
+
return
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
console.log('请求成功:', data)
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// ============================================
|
|
193
|
+
// 导出示例函数
|
|
194
|
+
// ============================================
|
|
195
|
+
|
|
196
|
+
export { example1, example2, example3 }
|
|
197
|
+
`;
|
|
198
|
+
const examplePath = path.join(moduleOutputDir, "example.ts");
|
|
199
|
+
fs.writeFileSync(examplePath, exampleContent, "utf-8");
|
|
200
|
+
console.log(`[${PLUGIN_NAME}] 生成示例文件: example.ts`);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
//#endregion
|
|
204
|
+
//#region src/generators/module-index.ts
|
|
205
|
+
/**
|
|
206
|
+
* 为每个模块生成 index.ts 导出文件
|
|
207
|
+
* 解决命名冲突:将 API 类重命名为 XxxApi,避免与接口类型同名
|
|
208
|
+
* 避免重复导出公共类型(HttpClient 等)
|
|
209
|
+
*/
|
|
210
|
+
function generateModuleIndexFile(moduleOutputDir, moduleName, apiClassName, isFirstModule) {
|
|
211
|
+
const apiInstanceName = apiClassName;
|
|
212
|
+
const moduleIndexContent = `/**
|
|
213
|
+
* ${apiClassName} 模块导出
|
|
214
|
+
* 由 vite-plugin-swagger-typescript-api 自动生成
|
|
215
|
+
*/
|
|
216
|
+
|
|
217
|
+
// 导出所有类型定义
|
|
218
|
+
export * from './Api'
|
|
219
|
+
|
|
220
|
+
// 重命名导出 API 类,避免与接口类型同名
|
|
221
|
+
export { ${apiClassName} as ${apiInstanceName} } from './Api'
|
|
222
|
+
${isFirstModule ? `
|
|
223
|
+
// 导出公共类型(只在第一个模块导出)
|
|
224
|
+
// HttpClient 是类,ContentType 是枚举,可以正常导出
|
|
225
|
+
export { HttpClient, ContentType } from './Api'
|
|
226
|
+
// ApiConfig, RequestParams, HttpResponse 是纯类型,使用 export type
|
|
227
|
+
export type { ApiConfig, RequestParams, HttpResponse } from './Api'` : ""}
|
|
228
|
+
`;
|
|
229
|
+
const moduleIndexPath = path.join(moduleOutputDir, "index.ts");
|
|
230
|
+
fs.writeFileSync(moduleIndexPath, moduleIndexContent, "utf-8");
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
//#endregion
|
|
234
|
+
//#region src/generators/entry-index.ts
|
|
235
|
+
/**
|
|
236
|
+
* 生成统一导出的入口文件 index.ts
|
|
237
|
+
*/
|
|
238
|
+
function generateIndexFile(outputDir, modules, options) {
|
|
239
|
+
if (modules.length === 0) return;
|
|
240
|
+
const firstModule = modules[0];
|
|
241
|
+
const firstModuleExport = `// ${firstModule.moduleName} API(包含公共类型:HttpClient, ApiConfig 等)
|
|
242
|
+
export * from './${firstModule.moduleName}'`;
|
|
243
|
+
const otherModuleExports = modules.slice(1).map(({ moduleName, apiClassName }) => {
|
|
244
|
+
return `// ${moduleName} API
|
|
245
|
+
export {
|
|
246
|
+
// API 类
|
|
247
|
+
${apiClassName},
|
|
248
|
+
// 模块特有的类型(接口、枚举、命名空间)
|
|
249
|
+
${apiClassName} as ${apiClassName}Client,
|
|
250
|
+
} from './${moduleName}'
|
|
251
|
+
|
|
252
|
+
// 导出 ${moduleName} 模块的所有类型(通过命名空间访问)
|
|
253
|
+
export * as ${apiClassName}Types from './${moduleName}'`;
|
|
254
|
+
}).join("\n\n");
|
|
255
|
+
const apiImports = modules.map(({ moduleName, apiClassName }) => {
|
|
256
|
+
return `import { ${apiClassName}, HttpClient as ${apiClassName}HttpClient } from './${moduleName}'`;
|
|
257
|
+
}).join("\n");
|
|
258
|
+
const apiInstances = modules.map(({ moduleName, apiClassName }) => {
|
|
259
|
+
const instanceName = moduleName.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
|
|
260
|
+
return `/** ${moduleName} API 实例(使用默认配置) */
|
|
261
|
+
export const ${instanceName}Api = new ${apiClassName}(new ${apiClassName}HttpClient())`;
|
|
262
|
+
}).join("\n\n");
|
|
263
|
+
const otherExportsSection = otherModuleExports ? `
|
|
264
|
+
|
|
265
|
+
// ============================================
|
|
266
|
+
// 其他模块导出(排除公共类型)
|
|
267
|
+
// ============================================
|
|
268
|
+
|
|
269
|
+
${otherModuleExports}` : "";
|
|
270
|
+
const indexContent = `/**
|
|
271
|
+
* 自动生成的 API 统一入口文件
|
|
272
|
+
* 由 vite-plugin-swagger-typescript-api 生成
|
|
273
|
+
*
|
|
274
|
+
* 注意:此文件由插件自动生成,请勿手动修改
|
|
275
|
+
*
|
|
276
|
+
* @example 简单使用方式
|
|
277
|
+
* \`\`\`typescript
|
|
278
|
+
* import { productApi, HttpClient } from './api'
|
|
279
|
+
*
|
|
280
|
+
* // 直接使用预配置的 API 实例
|
|
281
|
+
* const products = await productApi.products.productsList({ page: 1, limit: 10 })
|
|
282
|
+
* \`\`\`
|
|
283
|
+
*
|
|
284
|
+
* @example 自定义配置方式
|
|
285
|
+
* \`\`\`typescript
|
|
286
|
+
* import { ProductApi, HttpClient } from './api/product'
|
|
287
|
+
*
|
|
288
|
+
* // 创建自定义配置的 HTTP 客户端
|
|
289
|
+
* const http = new HttpClient({
|
|
290
|
+
* baseUrl: 'https://custom-api.example.com',
|
|
291
|
+
* headers: { Authorization: 'Bearer your-token' }
|
|
292
|
+
* })
|
|
293
|
+
*
|
|
294
|
+
* // 使用自定义客户端创建 API 实例
|
|
295
|
+
* const productApi = new ProductApi(http)
|
|
296
|
+
*
|
|
297
|
+
* // 发起请求
|
|
298
|
+
* const products = await productApi.products.productsList({ page: 1 })
|
|
299
|
+
* \`\`\`
|
|
300
|
+
*/
|
|
301
|
+
|
|
302
|
+
${firstModuleExport}
|
|
303
|
+
${otherExportsSection}
|
|
304
|
+
|
|
305
|
+
// ============================================
|
|
306
|
+
// 预配置的 API 实例(开箱即用)
|
|
307
|
+
// ============================================
|
|
308
|
+
|
|
309
|
+
${apiImports}
|
|
310
|
+
|
|
311
|
+
${apiInstances}
|
|
312
|
+
`;
|
|
313
|
+
const indexPath = path.join(outputDir, "index.ts");
|
|
314
|
+
fs.writeFileSync(indexPath, indexContent, "utf-8");
|
|
315
|
+
console.log(`[${PLUGIN_NAME}] 生成入口文件: index.ts`);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
//#endregion
|
|
319
|
+
//#region src/generators/request.ts
|
|
320
|
+
/**
|
|
321
|
+
* 生成基于 Fetch 的 HTTP 请求工具函数
|
|
322
|
+
*/
|
|
323
|
+
function generateFetchRequestUtil(baseURL) {
|
|
324
|
+
return `/**
|
|
325
|
+
* HTTP 请求工具函数(基于 Fetch)
|
|
326
|
+
* 由 vite-plugin-swagger-typescript-api 生成
|
|
327
|
+
*/
|
|
328
|
+
|
|
329
|
+
/** 请求配置选项 */
|
|
330
|
+
interface RequestConfig extends Omit<RequestInit, 'body'> {
|
|
331
|
+
/** 请求参数(查询字符串) */
|
|
332
|
+
params?: Record<string, any>
|
|
333
|
+
/** 请求体数据 */
|
|
334
|
+
body?: any
|
|
335
|
+
/** 请求超时时间(毫秒) */
|
|
336
|
+
timeout?: number
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/** 响应结果包装 */
|
|
340
|
+
interface ResponseResult<T> {
|
|
341
|
+
data: T
|
|
342
|
+
status: number
|
|
343
|
+
statusText: string
|
|
344
|
+
headers: Headers
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/** 基础 URL 配置 */
|
|
348
|
+
const BASE_URL = '${baseURL}'
|
|
349
|
+
|
|
350
|
+
/** 默认请求头 */
|
|
351
|
+
const defaultHeaders: HeadersInit = {
|
|
352
|
+
'Content-Type': 'application/json',
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/**
|
|
356
|
+
* 获取认证 Token(可自定义实现)
|
|
357
|
+
*/
|
|
358
|
+
function getAuthToken(): string | null {
|
|
359
|
+
// 从 localStorage 或其他存储中获取 token
|
|
360
|
+
// return localStorage.getItem('token')
|
|
361
|
+
return null
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/**
|
|
365
|
+
* 构建查询字符串
|
|
366
|
+
*/
|
|
367
|
+
function buildQueryString(params: Record<string, any>): string {
|
|
368
|
+
const searchParams = new URLSearchParams()
|
|
369
|
+
Object.entries(params).forEach(([key, value]) => {
|
|
370
|
+
if (value !== undefined && value !== null) {
|
|
371
|
+
searchParams.append(key, String(value))
|
|
372
|
+
}
|
|
373
|
+
})
|
|
374
|
+
return searchParams.toString()
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
/**
|
|
378
|
+
* 创建带超时的 fetch 请求
|
|
379
|
+
*/
|
|
380
|
+
async function fetchWithTimeout(
|
|
381
|
+
url: string,
|
|
382
|
+
config: RequestInit,
|
|
383
|
+
timeout: number = 10000,
|
|
384
|
+
): Promise<Response> {
|
|
385
|
+
const controller = new AbortController()
|
|
386
|
+
const timeoutId = setTimeout(() => controller.abort(), timeout)
|
|
387
|
+
|
|
388
|
+
try {
|
|
389
|
+
const response = await fetch(url, {
|
|
390
|
+
...config,
|
|
391
|
+
signal: controller.signal,
|
|
392
|
+
})
|
|
393
|
+
return response
|
|
394
|
+
}
|
|
395
|
+
finally {
|
|
396
|
+
clearTimeout(timeoutId)
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
/**
|
|
401
|
+
* 核心请求方法
|
|
402
|
+
*/
|
|
403
|
+
async function request<T>(
|
|
404
|
+
method: string,
|
|
405
|
+
url: string,
|
|
406
|
+
config: RequestConfig = {},
|
|
407
|
+
): Promise<ResponseResult<T>> {
|
|
408
|
+
const { params, body, timeout = 10000, ...init } = config
|
|
409
|
+
|
|
410
|
+
// 构建完整 URL
|
|
411
|
+
let fullUrl = \`\${BASE_URL}\${url}\`
|
|
412
|
+
if (params && Object.keys(params).length > 0) {
|
|
413
|
+
fullUrl += \`?\${buildQueryString(params)}\`
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
// 设置请求头
|
|
417
|
+
const headers: HeadersInit = {
|
|
418
|
+
...defaultHeaders,
|
|
419
|
+
...init.headers,
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
// 添加认证 Token
|
|
423
|
+
const token = getAuthToken()
|
|
424
|
+
if (token) {
|
|
425
|
+
(headers as Record<string, string>)['Authorization'] = \`Bearer \${token}\`
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
// 发送请求
|
|
429
|
+
const response = await fetchWithTimeout(
|
|
430
|
+
fullUrl,
|
|
431
|
+
{
|
|
432
|
+
...init,
|
|
433
|
+
method,
|
|
434
|
+
headers,
|
|
435
|
+
body: body ? JSON.stringify(body) : undefined,
|
|
436
|
+
},
|
|
437
|
+
timeout,
|
|
438
|
+
)
|
|
439
|
+
|
|
440
|
+
// 处理响应
|
|
441
|
+
if (!response.ok) {
|
|
442
|
+
const errorText = await response.text()
|
|
443
|
+
throw new Error(\`HTTP \${response.status}: \${errorText || response.statusText}\`)
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
// 解析 JSON 响应
|
|
447
|
+
const data = await response.json()
|
|
448
|
+
|
|
449
|
+
return {
|
|
450
|
+
data,
|
|
451
|
+
status: response.status,
|
|
452
|
+
statusText: response.statusText,
|
|
453
|
+
headers: response.headers,
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
/**
|
|
458
|
+
* HTTP 请求工具对象
|
|
459
|
+
*/
|
|
460
|
+
export const http = {
|
|
461
|
+
/**
|
|
462
|
+
* GET 请求
|
|
463
|
+
*/
|
|
464
|
+
get: <T>(url: string, params?: Record<string, any>, config?: RequestConfig) =>
|
|
465
|
+
request<T>('GET', url, { ...config, params }),
|
|
466
|
+
|
|
467
|
+
/**
|
|
468
|
+
* POST 请求
|
|
469
|
+
*/
|
|
470
|
+
post: <T>(url: string, body?: any, config?: RequestConfig) =>
|
|
471
|
+
request<T>('POST', url, { ...config, body }),
|
|
472
|
+
|
|
473
|
+
/**
|
|
474
|
+
* PUT 请求
|
|
475
|
+
*/
|
|
476
|
+
put: <T>(url: string, body?: any, config?: RequestConfig) =>
|
|
477
|
+
request<T>('PUT', url, { ...config, body }),
|
|
478
|
+
|
|
479
|
+
/**
|
|
480
|
+
* DELETE 请求
|
|
481
|
+
*/
|
|
482
|
+
delete: <T>(url: string, config?: RequestConfig) =>
|
|
483
|
+
request<T>('DELETE', url, config),
|
|
484
|
+
|
|
485
|
+
/**
|
|
486
|
+
* PATCH 请求
|
|
487
|
+
*/
|
|
488
|
+
patch: <T>(url: string, body?: any, config?: RequestConfig) =>
|
|
489
|
+
request<T>('PATCH', url, { ...config, body }),
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
/** 默认导出 http 对象 */
|
|
493
|
+
export default http
|
|
494
|
+
`;
|
|
495
|
+
}
|
|
496
|
+
/**
|
|
497
|
+
* 生成基于 Axios 的 HTTP 请求工具函数
|
|
498
|
+
*/
|
|
499
|
+
function generateAxiosRequestUtil(baseURL) {
|
|
500
|
+
return `/**
|
|
501
|
+
* HTTP 请求工具函数(基于 Axios)
|
|
502
|
+
* 由 vite-plugin-swagger-typescript-api 生成
|
|
503
|
+
*
|
|
504
|
+
* 注意:需要安装 axios 依赖
|
|
505
|
+
*/
|
|
506
|
+
|
|
507
|
+
import axios, { type AxiosInstance, type AxiosRequestConfig } from 'axios'
|
|
508
|
+
|
|
509
|
+
/** 创建 axios 实例 */
|
|
510
|
+
const instance: AxiosInstance = axios.create({
|
|
511
|
+
baseURL: '${baseURL}',
|
|
512
|
+
timeout: 10000,
|
|
513
|
+
headers: {
|
|
514
|
+
'Content-Type': 'application/json',
|
|
515
|
+
},
|
|
516
|
+
})
|
|
517
|
+
|
|
518
|
+
/** 请求拦截器 */
|
|
519
|
+
instance.interceptors.request.use(
|
|
520
|
+
(config) => {
|
|
521
|
+
// 在这里可以添加 token 等认证信息
|
|
522
|
+
// const token = localStorage.getItem('token')
|
|
523
|
+
// if (token) {
|
|
524
|
+
// config.headers.Authorization = \`Bearer \${token}\`
|
|
525
|
+
// }
|
|
526
|
+
return config
|
|
527
|
+
},
|
|
528
|
+
(error) => {
|
|
529
|
+
return Promise.reject(error)
|
|
530
|
+
},
|
|
531
|
+
)
|
|
532
|
+
|
|
533
|
+
/** 响应拦截器 */
|
|
534
|
+
instance.interceptors.response.use(
|
|
535
|
+
(response) => {
|
|
536
|
+
return response.data
|
|
537
|
+
},
|
|
538
|
+
(error) => {
|
|
539
|
+
// 统一错误处理
|
|
540
|
+
console.error('API Error:', error.message)
|
|
541
|
+
return Promise.reject(error)
|
|
542
|
+
},
|
|
543
|
+
)
|
|
544
|
+
|
|
545
|
+
/**
|
|
546
|
+
* HTTP 请求工具对象
|
|
547
|
+
*/
|
|
548
|
+
export const request = {
|
|
549
|
+
get: <T>(url: string, params?: Record<string, any>, config?: AxiosRequestConfig) =>
|
|
550
|
+
instance.get<T>(url, { params, ...config }),
|
|
551
|
+
|
|
552
|
+
post: <T>(url: string, data?: any, config?: AxiosRequestConfig) =>
|
|
553
|
+
instance.post<T>(url, data, config),
|
|
554
|
+
|
|
555
|
+
put: <T>(url: string, data?: any, config?: AxiosRequestConfig) =>
|
|
556
|
+
instance.put<T>(url, data, config),
|
|
557
|
+
|
|
558
|
+
delete: <T>(url: string, config?: AxiosRequestConfig) =>
|
|
559
|
+
instance.delete<T>(url, config),
|
|
560
|
+
|
|
561
|
+
patch: <T>(url: string, data?: any, config?: AxiosRequestConfig) =>
|
|
562
|
+
instance.patch<T>(url, data, config),
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
/** 默认导出 axios 实例 */
|
|
566
|
+
export default instance
|
|
567
|
+
|
|
568
|
+
/** 导出 axios 实例供高级使用 */
|
|
569
|
+
export { instance as axios }
|
|
570
|
+
`;
|
|
571
|
+
}
|
|
572
|
+
/**
|
|
573
|
+
* 生成 HTTP 请求工具函数
|
|
574
|
+
*/
|
|
575
|
+
function generateRequestUtil(outputDir, options) {
|
|
576
|
+
if (!options.baseURL) return;
|
|
577
|
+
const requestContent = options.httpClient === "axios" ? generateAxiosRequestUtil(options.baseURL) : generateFetchRequestUtil(options.baseURL);
|
|
578
|
+
const requestPath = path.join(outputDir, "request.ts");
|
|
579
|
+
fs.writeFileSync(requestPath, requestContent, "utf-8");
|
|
580
|
+
console.log(`[${PLUGIN_NAME}] 生成请求工具: request.ts (${options.httpClient})`);
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
//#endregion
|
|
584
|
+
//#region src/core/scanner.ts
|
|
585
|
+
/**
|
|
586
|
+
* 扫描并生成所有 API 代码
|
|
587
|
+
*/
|
|
588
|
+
async function scanAndGenerateApis(options, isRebuild = false) {
|
|
589
|
+
const rootDir = options.rootDir || process.cwd();
|
|
590
|
+
const outputDir = path.resolve(rootDir, options.outputDir);
|
|
591
|
+
if (isRebuild) console.log(`\n[${PLUGIN_NAME}] 检测到文件变化,重新生成 API...`);
|
|
592
|
+
else {
|
|
593
|
+
console.log(`\n[${PLUGIN_NAME}] 开始扫描 YAML 文件...`);
|
|
594
|
+
console.log(`[${PLUGIN_NAME}] 项目根目录: ${rootDir}`);
|
|
595
|
+
console.log(`[${PLUGIN_NAME}] 输出目录: ${outputDir}`);
|
|
596
|
+
}
|
|
597
|
+
const yamlDirs = findYamlDirectories(rootDir);
|
|
598
|
+
if (yamlDirs.length === 0) {
|
|
599
|
+
console.log(`[${PLUGIN_NAME}] 未找到名为 "yaml" 的文件夹`);
|
|
600
|
+
return [];
|
|
601
|
+
}
|
|
602
|
+
console.log(`[${PLUGIN_NAME}] 找到 ${yamlDirs.length} 个 yaml 目录:`);
|
|
603
|
+
yamlDirs.forEach((dir) => {
|
|
604
|
+
console.log(`[${PLUGIN_NAME}] - ${path.relative(rootDir, dir)}`);
|
|
605
|
+
});
|
|
606
|
+
const yamlFiles = [];
|
|
607
|
+
for (const dir of yamlDirs) yamlFiles.push(...findYamlFiles(dir));
|
|
608
|
+
if (yamlFiles.length === 0) {
|
|
609
|
+
console.log(`[${PLUGIN_NAME}] 未找到 .yaml 或 .yml 文件`);
|
|
610
|
+
return [];
|
|
611
|
+
}
|
|
612
|
+
console.log(`[${PLUGIN_NAME}] 找到 ${yamlFiles.length} 个 YAML 文件:`);
|
|
613
|
+
yamlFiles.forEach((file) => {
|
|
614
|
+
console.log(`[${PLUGIN_NAME}] - ${path.relative(rootDir, file)}`);
|
|
615
|
+
});
|
|
616
|
+
const { ensureDir: ensureDir$1 } = await import("./utils.js");
|
|
617
|
+
ensureDir$1(outputDir);
|
|
618
|
+
console.log(`\n[${PLUGIN_NAME}] 开始生成 TypeScript API 代码...`);
|
|
619
|
+
const modules = [];
|
|
620
|
+
for (const yamlFile of yamlFiles) try {
|
|
621
|
+
const moduleResult = await generateTypeScriptFromYaml(yamlFile, outputDir, options);
|
|
622
|
+
modules.push(moduleResult);
|
|
623
|
+
} catch (error) {
|
|
624
|
+
console.error(`[${PLUGIN_NAME}] 跳过文件: ${yamlFile}`);
|
|
625
|
+
}
|
|
626
|
+
modules.forEach(({ moduleName, apiClassName }, index) => {
|
|
627
|
+
const moduleOutputDir = path.join(outputDir, moduleName);
|
|
628
|
+
generateModuleIndexFile(moduleOutputDir, moduleName, apiClassName, index === 0);
|
|
629
|
+
generateModuleExampleFile(moduleOutputDir, moduleName, apiClassName);
|
|
630
|
+
});
|
|
631
|
+
if (modules.length > 0) {
|
|
632
|
+
generateIndexFile(outputDir, modules, options);
|
|
633
|
+
generateRequestUtil(outputDir, options);
|
|
634
|
+
}
|
|
635
|
+
console.log(`\n[${PLUGIN_NAME}] ✅ 成功生成 ${modules.length} 个 API 模块\n`);
|
|
636
|
+
return modules.map((m) => m.moduleName);
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
//#endregion
|
|
640
|
+
//#region src/core/plugin.ts
|
|
641
|
+
/**
|
|
642
|
+
* 插件工厂函数
|
|
643
|
+
*/
|
|
644
|
+
const unpluginFactory = (options = {}) => {
|
|
645
|
+
const mergedOptions = {
|
|
646
|
+
...DEFAULT_OPTIONS,
|
|
647
|
+
...options
|
|
648
|
+
};
|
|
649
|
+
let hasGenerated = false;
|
|
650
|
+
return {
|
|
651
|
+
name: PLUGIN_NAME,
|
|
652
|
+
vite: {
|
|
653
|
+
configResolved() {
|
|
654
|
+
if (!hasGenerated) {
|
|
655
|
+
hasGenerated = true;
|
|
656
|
+
scanAndGenerateApis(mergedOptions);
|
|
657
|
+
}
|
|
658
|
+
},
|
|
659
|
+
configureServer(server) {
|
|
660
|
+
if (mergedOptions.watch) server.watcher.on("change", (file) => {
|
|
661
|
+
if (file.endsWith(".yaml") || file.endsWith(".yml")) {
|
|
662
|
+
hasGenerated = false;
|
|
663
|
+
scanAndGenerateApis(mergedOptions, true);
|
|
664
|
+
}
|
|
665
|
+
});
|
|
666
|
+
}
|
|
667
|
+
},
|
|
668
|
+
async buildStart() {
|
|
669
|
+
if (!hasGenerated) {
|
|
670
|
+
hasGenerated = true;
|
|
671
|
+
await scanAndGenerateApis(mergedOptions);
|
|
672
|
+
}
|
|
673
|
+
},
|
|
674
|
+
webpack(compiler) {
|
|
675
|
+
compiler.hooks.beforeRun.tapPromise(PLUGIN_NAME, async () => {
|
|
676
|
+
if (!hasGenerated) {
|
|
677
|
+
hasGenerated = true;
|
|
678
|
+
await scanAndGenerateApis(mergedOptions);
|
|
679
|
+
}
|
|
680
|
+
});
|
|
681
|
+
}
|
|
682
|
+
};
|
|
683
|
+
};
|
|
684
|
+
const unplugin = /* @__PURE__ */ createUnplugin(unpluginFactory);
|
|
685
|
+
|
|
686
|
+
//#endregion
|
|
687
|
+
export { DEFAULT_OPTIONS, PLUGIN_NAME, unplugin, unpluginFactory };
|
package/dist/types.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { HttpClientType, MergedOptions, Options } from "./types-
|
|
1
|
+
import { HttpClientType, MergedOptions, Options } from "./types-BJUz_-Xc.js";
|
|
2
2
|
export { HttpClientType, MergedOptions, Options };
|
package/dist/utils.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { ensureDir, fileExists, findYamlDirectories, findYamlFiles, formatModuleName, getRelativePath, safeReaddir } from "./utils-
|
|
1
|
+
import { ensureDir, fileExists, findYamlDirectories, findYamlFiles, formatModuleName, getRelativePath, safeReaddir } from "./utils-DGqLkcWK.js";
|
|
2
2
|
|
|
3
3
|
export { ensureDir, fileExists, findYamlDirectories, findYamlFiles, formatModuleName, getRelativePath, safeReaddir };
|
package/dist/vite.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { Options } from "./types-
|
|
2
|
-
import * as
|
|
1
|
+
import { Options } from "./types-BJUz_-Xc.js";
|
|
2
|
+
import * as vite3 from "vite";
|
|
3
3
|
|
|
4
4
|
//#region src/vite.d.ts
|
|
5
|
-
declare const _default: (options?: Options | undefined) =>
|
|
5
|
+
declare const _default: (options?: Options | undefined) => vite3.Plugin<any> | vite3.Plugin<any>[];
|
|
6
6
|
|
|
7
7
|
//#endregion
|
|
8
8
|
export { _default as default };
|