@cnbcool/cnb-api-generate 1.2.5 → 1.2.7

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/client/index.ts CHANGED
@@ -498,7 +498,7 @@ async function main() {
498
498
  // 上传快捷命令:走完整上传流程(获取 URL → PUT 文件 → 返回结果)
499
499
  let data: any;
500
500
  if (shortcut?.upload) {
501
- data = await handleUpload(shortcut, formattedParams.data?.file, toolFunction, pathAndQueryParams);
501
+ data = await handleUpload(shortcut, formattedParams.data?.file, toolFunction, pathAndQueryParams as { repo: string; number?: string });
502
502
  } else {
503
503
  if (formattedParams.data) {
504
504
  toolsParam.push(formattedParams.data);
@@ -8,27 +8,148 @@
8
8
 
9
9
  import fs from 'fs';
10
10
  import nodePath from 'path';
11
- import { lookup as mimeLookup } from 'mime-types';
12
11
  import type { ResolvedShortcut } from '../shortcuts';
13
12
 
13
+ /** 常见文件扩展名 → MIME 类型映射(仅用于上传场景,不引入外部依赖) */
14
+ const MIME_MAP: Record<string, string> = {
15
+ '.txt': 'text/plain',
16
+ '.html': 'text/html', '.htm': 'text/html',
17
+ '.css': 'text/css',
18
+ '.csv': 'text/csv',
19
+ '.js': 'application/javascript', '.mjs': 'application/javascript',
20
+ '.json': 'application/json',
21
+ '.xml': 'application/xml',
22
+ '.pdf': 'application/pdf',
23
+ '.zip': 'application/zip',
24
+ '.gz': 'application/gzip', '.gzip': 'application/gzip',
25
+ '.tar': 'application/x-tar',
26
+ '.7z': 'application/x-7z-compressed',
27
+ '.rar': 'application/vnd.rar',
28
+ '.doc': 'application/msword',
29
+ '.docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
30
+ '.xls': 'application/vnd.ms-excel',
31
+ '.xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
32
+ '.ppt': 'application/vnd.ms-powerpoint',
33
+ '.pptx': 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
34
+ '.png': 'image/png',
35
+ '.jpg': 'image/jpeg', '.jpeg': 'image/jpeg',
36
+ '.gif': 'image/gif',
37
+ '.svg': 'image/svg+xml',
38
+ '.webp': 'image/webp',
39
+ '.ico': 'image/x-icon',
40
+ '.bmp': 'image/bmp',
41
+ '.mp3': 'audio/mpeg',
42
+ '.wav': 'audio/wav',
43
+ '.ogg': 'audio/ogg',
44
+ '.mp4': 'video/mp4',
45
+ '.webm': 'video/webm',
46
+ '.avi': 'video/x-msvideo',
47
+ '.mov': 'video/quicktime',
48
+ '.woff': 'font/woff', '.woff2': 'font/woff2',
49
+ '.ttf': 'font/ttf',
50
+ '.otf': 'font/otf',
51
+ '.md': 'text/markdown',
52
+ '.yaml': 'text/yaml', '.yml': 'text/yaml',
53
+ '.sh': 'application/x-sh',
54
+ '.py': 'text/x-python',
55
+ '.rb': 'text/x-ruby',
56
+ '.go': 'text/x-go',
57
+ '.rs': 'text/x-rust',
58
+ '.ts': 'text/typescript', '.tsx': 'text/typescript',
59
+ '.jsx': 'text/jsx',
60
+ '.java': 'text/x-java-source',
61
+ '.c': 'text/x-c', '.h': 'text/x-c',
62
+ '.cpp': 'text/x-c++src', '.hpp': 'text/x-c++hdr',
63
+ '.log': 'text/plain',
64
+ '.patch': 'text/x-diff', '.diff': 'text/x-diff',
65
+ };
66
+
67
+ function mimeLookup(filePath: string): string {
68
+ const ext = nodePath.extname(filePath).toLowerCase();
69
+ return MIME_MAP[ext] || 'application/octet-stream';
70
+ }
71
+
14
72
  // 上传文件大小上限:100MB
15
73
  const MAX_FILE_SIZE = 100 * 1024 * 1024;
16
74
 
75
+ /** 上传 API 的 path 参数 */
76
+ interface UploadPathParams {
77
+ repo: string;
78
+ number?: string;
79
+ }
80
+
81
+ /** Issue 上传请求体 */
82
+ interface IssueUploadBody {
83
+ name: string;
84
+ size: number;
85
+ content_type: string;
86
+ }
87
+
88
+ /** Pull 上传请求体 */
89
+ interface PullUploadBody {
90
+ name: string;
91
+ size: number;
92
+ }
93
+
94
+ /** 上传 API 函数签名 */
95
+ type UploadApiFunction = (
96
+ params: UploadPathParams,
97
+ body: IssueUploadBody | PullUploadBody,
98
+ ) => Promise<UploadApiResponse>;
99
+
100
+ /** 上传 API 响应 */
101
+ interface UploadApiResponse {
102
+ status?: number;
103
+ data?: {
104
+ upload_url?: string;
105
+ asset_link?: string;
106
+ assets?: { name?: string; path?: string };
107
+ token?: string;
108
+ };
109
+ }
110
+
111
+ /** 上传失败时的 data */
112
+ interface UploadErrorData {
113
+ error: string;
114
+ detail?: string;
115
+ }
116
+
117
+ /** Issue 上传成功时的 data */
118
+ interface IssueUploadData {
119
+ asset_link?: string;
120
+ name: string;
121
+ size: number;
122
+ }
123
+
124
+ /** Pull 上传成功时的 data */
125
+ interface PullUploadData {
126
+ name: string;
127
+ path?: string;
128
+ size: number;
129
+ token?: string;
130
+ }
131
+
132
+ /** handleUpload 的返回值 */
133
+ interface UploadResult {
134
+ status: number;
135
+ data: UploadErrorData | IssueUploadData | PullUploadData;
136
+ }
137
+
17
138
  /**
18
139
  * 处理完整上传流程
19
140
  * @param shortcut 解析后的快捷命令
20
141
  * @param filePath 本地文件路径
21
142
  * @param toolFunction 原始上传 API 函数(获取 upload_url)
22
- * @param toolsParam API 调用的 path 参数(repo 或 {repo, number})
143
+ * @param pathParams API 调用的 path 参数(repo 或 {repo, number})
23
144
  */
24
145
  export async function handleUpload(
25
146
  shortcut: ResolvedShortcut,
26
- filePath: any,
27
- toolFunction: (...args: any[]) => Promise<any>,
28
- toolsParam: any,
29
- ): Promise<any> {
147
+ filePath: string | undefined,
148
+ toolFunction: UploadApiFunction,
149
+ pathParams: UploadPathParams,
150
+ ): Promise<UploadResult> {
30
151
  // 校验 file 参数
31
- if (!filePath || typeof filePath !== 'string') {
152
+ if (!filePath) {
32
153
  return { status: 400, data: { error: `上传命令需要指定文件路径,如: --data '{"file":"./path/to/file"}'` } };
33
154
  }
34
155
 
@@ -52,7 +173,7 @@ export async function handleUpload(
52
173
  return { status: 400, data: { error: `文件过大 (${(fileSize / 1024 / 1024).toFixed(1)}MB),上限 ${MAX_FILE_SIZE / 1024 / 1024}MB` } };
53
174
  }
54
175
 
55
- const contentType = mimeLookup(filePath) || 'application/octet-stream';
176
+ const contentType = mimeLookup(filePath);
56
177
 
57
178
  // 2. 调用上传 API 获取 upload_url
58
179
  const isIssueUpload = shortcut.module === 'issues';
@@ -60,14 +181,14 @@ export async function handleUpload(
60
181
  ? { name: fileName, size: fileSize, content_type: contentType }
61
182
  : { name: fileName, size: fileSize };
62
183
 
63
- const uploadResponse = await toolFunction(toolsParam, requestBody);
184
+ const uploadResponse = await toolFunction(pathParams, requestBody);
64
185
 
65
- // 提取 upload_url(兼容标准响应和裸响应)
66
- const responseData = uploadResponse?.data ?? uploadResponse;
186
+ // 提取 upload_url
187
+ const responseData = uploadResponse?.data;
67
188
  const uploadUrl = responseData?.upload_url;
68
189
 
69
190
  if (!uploadUrl) {
70
- return uploadResponse; // 获取 URL 失败,直接返回原始错误
191
+ return uploadResponse as unknown as UploadResult; // 获取 URL 失败,直接返回原始错误
71
192
  }
72
193
 
73
194
  // 3. PUT 文件内容到 upload_url(流式读取,避免大文件撑爆内存)
@@ -78,15 +199,22 @@ export async function handleUpload(
78
199
  'Content-Length': String(fileSize),
79
200
  };
80
201
 
81
- const putResponse = await fetch(uploadUrl, {
82
- method: 'PUT',
83
- headers: putHeaders,
84
- body: fileStream as any,
85
- // @ts-ignore duplex required for streaming body in Node.js fetch
86
- duplex: 'half',
87
- });
202
+ let putResponse: Response;
203
+ try {
204
+ putResponse = await fetch(uploadUrl, {
205
+ method: 'PUT',
206
+ headers: putHeaders,
207
+ body: fileStream as any,
208
+ // @ts-ignore duplex required for streaming body in Node.js fetch
209
+ duplex: 'half',
210
+ });
211
+ } catch (e) {
212
+ fileStream.destroy();
213
+ throw e;
214
+ }
88
215
 
89
216
  if (!putResponse.ok) {
217
+ fileStream.destroy();
90
218
  const errText = await putResponse.text().catch(() => '');
91
219
  return {
92
220
  status: putResponse.status,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cnbcool/cnb-api-generate",
3
- "version": "1.2.5",
3
+ "version": "1.2.7",
4
4
  "main": "./built/index.js",
5
5
  "module": "./src/index.ts",
6
6
  "types": "./src/index.ts",