@openclaw-china/shared 0.1.18 → 0.1.19
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/package.json +5 -3
- package/src/file/file-utils.test.ts +141 -0
- package/src/file/file-utils.ts +284 -0
- package/src/file/index.ts +10 -0
- package/src/index.ts +1 -0
- package/src/logger/index.ts +1 -1
- package/src/logger/logger.ts +51 -51
- package/vitest.config.ts +8 -0
package/package.json
CHANGED
|
@@ -1,16 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openclaw-china/shared",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.19",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": "./src/index.ts"
|
|
7
7
|
},
|
|
8
8
|
"scripts": {
|
|
9
9
|
"build": "tsc",
|
|
10
|
-
"dev": "tsc --watch"
|
|
10
|
+
"dev": "tsc --watch",
|
|
11
|
+
"test": "vitest --run"
|
|
11
12
|
},
|
|
12
13
|
"devDependencies": {
|
|
13
|
-
"typescript": "^5.7.0"
|
|
14
|
+
"typescript": "^5.7.0",
|
|
15
|
+
"vitest": "^2.1.0"
|
|
14
16
|
},
|
|
15
17
|
"private": false
|
|
16
18
|
}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unit Tests for File Utilities
|
|
3
|
+
*
|
|
4
|
+
* Feature: dingtalk-media-receive
|
|
5
|
+
* Validates: Requirements 5.1-5.8, 6.1-6.6
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { describe, it, expect } from "vitest";
|
|
9
|
+
import { resolveFileCategory, resolveExtension } from "./file-utils.js";
|
|
10
|
+
|
|
11
|
+
describe("resolveFileCategory", () => {
|
|
12
|
+
// Image categorization (Requirement 5.1)
|
|
13
|
+
it("should categorize image MIME types", () => {
|
|
14
|
+
expect(resolveFileCategory("image/jpeg")).toBe("image");
|
|
15
|
+
expect(resolveFileCategory("image/png")).toBe("image");
|
|
16
|
+
expect(resolveFileCategory("image/gif")).toBe("image");
|
|
17
|
+
expect(resolveFileCategory("image/webp")).toBe("image");
|
|
18
|
+
expect(resolveFileCategory("image/bmp")).toBe("image");
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
// Audio categorization (Requirement 5.2)
|
|
22
|
+
it("should categorize audio MIME types", () => {
|
|
23
|
+
expect(resolveFileCategory("audio/mpeg")).toBe("audio");
|
|
24
|
+
expect(resolveFileCategory("audio/wav")).toBe("audio");
|
|
25
|
+
expect(resolveFileCategory("audio/ogg")).toBe("audio");
|
|
26
|
+
expect(resolveFileCategory("audio/amr")).toBe("audio");
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
// Video categorization (Requirement 5.3)
|
|
30
|
+
it("should categorize video MIME types", () => {
|
|
31
|
+
expect(resolveFileCategory("video/mp4")).toBe("video");
|
|
32
|
+
expect(resolveFileCategory("video/quicktime")).toBe("video");
|
|
33
|
+
expect(resolveFileCategory("video/webm")).toBe("video");
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
// Document categorization (Requirement 5.4)
|
|
37
|
+
it("should categorize document MIME types", () => {
|
|
38
|
+
expect(resolveFileCategory("application/pdf")).toBe("document");
|
|
39
|
+
expect(resolveFileCategory("application/msword")).toBe("document");
|
|
40
|
+
expect(resolveFileCategory("text/plain")).toBe("document");
|
|
41
|
+
expect(resolveFileCategory("text/markdown")).toBe("document");
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
// Archive categorization (Requirement 5.5)
|
|
45
|
+
it("should categorize archive MIME types", () => {
|
|
46
|
+
expect(resolveFileCategory("application/zip")).toBe("archive");
|
|
47
|
+
expect(resolveFileCategory("application/x-rar-compressed")).toBe("archive");
|
|
48
|
+
expect(resolveFileCategory("application/x-7z-compressed")).toBe("archive");
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
// Code categorization (Requirement 5.6)
|
|
52
|
+
it("should categorize code MIME types", () => {
|
|
53
|
+
expect(resolveFileCategory("application/json")).toBe("code");
|
|
54
|
+
expect(resolveFileCategory("text/html")).toBe("code");
|
|
55
|
+
expect(resolveFileCategory("text/css")).toBe("code");
|
|
56
|
+
expect(resolveFileCategory("text/javascript")).toBe("code");
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
// Extension fallback (Requirement 5.8)
|
|
60
|
+
it("should use extension fallback when MIME type is unknown", () => {
|
|
61
|
+
expect(resolveFileCategory("application/octet-stream", "photo.jpg")).toBe("image");
|
|
62
|
+
expect(resolveFileCategory("application/octet-stream", "song.mp3")).toBe("audio");
|
|
63
|
+
expect(resolveFileCategory("application/octet-stream", "movie.mp4")).toBe("video");
|
|
64
|
+
expect(resolveFileCategory("application/octet-stream", "doc.pdf")).toBe("document");
|
|
65
|
+
expect(resolveFileCategory("application/octet-stream", "archive.zip")).toBe("archive");
|
|
66
|
+
expect(resolveFileCategory("application/octet-stream", "script.py")).toBe("code");
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
// Other category (Requirement 5.7)
|
|
70
|
+
it("should return 'other' for unknown types", () => {
|
|
71
|
+
expect(resolveFileCategory("application/octet-stream")).toBe("other");
|
|
72
|
+
expect(resolveFileCategory("application/unknown")).toBe("other");
|
|
73
|
+
expect(resolveFileCategory("application/octet-stream", "file.xyz")).toBe("other");
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
// MIME type normalization
|
|
77
|
+
it("should handle MIME types with parameters", () => {
|
|
78
|
+
expect(resolveFileCategory("image/jpeg; charset=utf-8")).toBe("image");
|
|
79
|
+
expect(resolveFileCategory("text/plain; charset=utf-8")).toBe("document");
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
describe("resolveExtension", () => {
|
|
84
|
+
// Image extensions (Requirement 6.1)
|
|
85
|
+
it("should resolve image MIME types to extensions", () => {
|
|
86
|
+
expect(resolveExtension("image/jpeg")).toBe(".jpg");
|
|
87
|
+
expect(resolveExtension("image/png")).toBe(".png");
|
|
88
|
+
expect(resolveExtension("image/gif")).toBe(".gif");
|
|
89
|
+
expect(resolveExtension("image/webp")).toBe(".webp");
|
|
90
|
+
expect(resolveExtension("image/bmp")).toBe(".bmp");
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
// Audio extensions (Requirement 6.2)
|
|
94
|
+
it("should resolve audio MIME types to extensions", () => {
|
|
95
|
+
expect(resolveExtension("audio/mpeg")).toBe(".mp3");
|
|
96
|
+
expect(resolveExtension("audio/wav")).toBe(".wav");
|
|
97
|
+
expect(resolveExtension("audio/ogg")).toBe(".ogg");
|
|
98
|
+
expect(resolveExtension("audio/amr")).toBe(".amr");
|
|
99
|
+
expect(resolveExtension("audio/x-m4a")).toBe(".m4a");
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
// Video extensions (Requirement 6.3)
|
|
103
|
+
it("should resolve video MIME types to extensions", () => {
|
|
104
|
+
expect(resolveExtension("video/mp4")).toBe(".mp4");
|
|
105
|
+
expect(resolveExtension("video/quicktime")).toBe(".mov");
|
|
106
|
+
expect(resolveExtension("video/x-msvideo")).toBe(".avi");
|
|
107
|
+
expect(resolveExtension("video/webm")).toBe(".webm");
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
// Document extensions (Requirement 6.4)
|
|
111
|
+
it("should resolve document MIME types to extensions", () => {
|
|
112
|
+
expect(resolveExtension("application/pdf")).toBe(".pdf");
|
|
113
|
+
expect(resolveExtension("application/msword")).toBe(".doc");
|
|
114
|
+
expect(resolveExtension("application/vnd.openxmlformats-officedocument.wordprocessingml.document")).toBe(".docx");
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
// Default extension (Requirement 6.5)
|
|
118
|
+
it("should return .bin for unknown MIME types", () => {
|
|
119
|
+
expect(resolveExtension("application/unknown")).toBe(".bin");
|
|
120
|
+
expect(resolveExtension("application/octet-stream")).toBe(".bin");
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
// fileName precedence (Requirement 6.6)
|
|
124
|
+
it("should use fileName extension when provided", () => {
|
|
125
|
+
expect(resolveExtension("application/octet-stream", "photo.jpg")).toBe(".jpg");
|
|
126
|
+
expect(resolveExtension("image/png", "custom.jpeg")).toBe(".jpeg");
|
|
127
|
+
expect(resolveExtension("application/unknown", "document.pdf")).toBe(".pdf");
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
// MIME type normalization
|
|
131
|
+
it("should handle MIME types with parameters", () => {
|
|
132
|
+
expect(resolveExtension("image/jpeg; charset=utf-8")).toBe(".jpg");
|
|
133
|
+
expect(resolveExtension("audio/mpeg; bitrate=320")).toBe(".mp3");
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
// Edge cases
|
|
137
|
+
it("should handle fileName without extension", () => {
|
|
138
|
+
expect(resolveExtension("image/jpeg", "photo")).toBe(".jpg");
|
|
139
|
+
expect(resolveExtension("application/unknown", "noext")).toBe(".bin");
|
|
140
|
+
});
|
|
141
|
+
});
|
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File utilities for categorizing and resolving file extensions
|
|
3
|
+
* @module @openclaw-china/shared/file
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* File category for processing strategy
|
|
8
|
+
*/
|
|
9
|
+
export type FileCategory =
|
|
10
|
+
| "image"
|
|
11
|
+
| "audio"
|
|
12
|
+
| "video"
|
|
13
|
+
| "document"
|
|
14
|
+
| "archive"
|
|
15
|
+
| "code"
|
|
16
|
+
| "other";
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* MIME type to extension mapping
|
|
20
|
+
*/
|
|
21
|
+
const MIME_TO_EXTENSION: Record<string, string> = {
|
|
22
|
+
// Images
|
|
23
|
+
"image/jpeg": ".jpg",
|
|
24
|
+
"image/png": ".png",
|
|
25
|
+
"image/gif": ".gif",
|
|
26
|
+
"image/webp": ".webp",
|
|
27
|
+
"image/bmp": ".bmp",
|
|
28
|
+
|
|
29
|
+
// Audio
|
|
30
|
+
"audio/mpeg": ".mp3",
|
|
31
|
+
"audio/wav": ".wav",
|
|
32
|
+
"audio/ogg": ".ogg",
|
|
33
|
+
"audio/amr": ".amr",
|
|
34
|
+
"audio/x-m4a": ".m4a",
|
|
35
|
+
|
|
36
|
+
// Video
|
|
37
|
+
"video/mp4": ".mp4",
|
|
38
|
+
"video/quicktime": ".mov",
|
|
39
|
+
"video/x-msvideo": ".avi",
|
|
40
|
+
"video/webm": ".webm",
|
|
41
|
+
|
|
42
|
+
// Documents
|
|
43
|
+
"application/pdf": ".pdf",
|
|
44
|
+
"application/msword": ".doc",
|
|
45
|
+
"application/vnd.openxmlformats-officedocument.wordprocessingml.document":
|
|
46
|
+
".docx",
|
|
47
|
+
"application/vnd.ms-excel": ".xls",
|
|
48
|
+
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": ".xlsx",
|
|
49
|
+
"application/vnd.ms-powerpoint": ".ppt",
|
|
50
|
+
"application/vnd.openxmlformats-officedocument.presentationml.presentation":
|
|
51
|
+
".pptx",
|
|
52
|
+
"application/rtf": ".rtf",
|
|
53
|
+
"application/vnd.oasis.opendocument.text": ".odt",
|
|
54
|
+
"application/vnd.oasis.opendocument.spreadsheet": ".ods",
|
|
55
|
+
"text/plain": ".txt",
|
|
56
|
+
"text/markdown": ".md",
|
|
57
|
+
"text/csv": ".csv",
|
|
58
|
+
|
|
59
|
+
// Archives
|
|
60
|
+
"application/zip": ".zip",
|
|
61
|
+
"application/x-rar-compressed": ".rar",
|
|
62
|
+
"application/vnd.rar": ".rar",
|
|
63
|
+
"application/x-7z-compressed": ".7z",
|
|
64
|
+
"application/x-tar": ".tar",
|
|
65
|
+
"application/gzip": ".gz",
|
|
66
|
+
"application/x-gzip": ".gz",
|
|
67
|
+
"application/x-bzip2": ".bz2",
|
|
68
|
+
|
|
69
|
+
// Code
|
|
70
|
+
"application/json": ".json",
|
|
71
|
+
"application/xml": ".xml",
|
|
72
|
+
"text/xml": ".xml",
|
|
73
|
+
"text/html": ".html",
|
|
74
|
+
"text/css": ".css",
|
|
75
|
+
"text/javascript": ".js",
|
|
76
|
+
"application/javascript": ".js",
|
|
77
|
+
"text/x-python": ".py",
|
|
78
|
+
"text/x-java-source": ".java",
|
|
79
|
+
"text/x-c": ".c",
|
|
80
|
+
"text/x-yaml": ".yaml",
|
|
81
|
+
"application/x-yaml": ".yaml",
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* MIME type to category mapping for non-prefix-based types
|
|
86
|
+
*/
|
|
87
|
+
const CATEGORY_BY_MIME: Record<string, FileCategory> = {
|
|
88
|
+
// Documents
|
|
89
|
+
"application/pdf": "document",
|
|
90
|
+
"application/msword": "document",
|
|
91
|
+
"application/vnd.openxmlformats-officedocument.wordprocessingml.document":
|
|
92
|
+
"document",
|
|
93
|
+
"application/vnd.ms-excel": "document",
|
|
94
|
+
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet":
|
|
95
|
+
"document",
|
|
96
|
+
"application/vnd.ms-powerpoint": "document",
|
|
97
|
+
"application/vnd.openxmlformats-officedocument.presentationml.presentation":
|
|
98
|
+
"document",
|
|
99
|
+
"application/rtf": "document",
|
|
100
|
+
"application/vnd.oasis.opendocument.text": "document",
|
|
101
|
+
"application/vnd.oasis.opendocument.spreadsheet": "document",
|
|
102
|
+
"text/plain": "document",
|
|
103
|
+
"text/markdown": "document",
|
|
104
|
+
"text/csv": "document",
|
|
105
|
+
// Archives
|
|
106
|
+
"application/zip": "archive",
|
|
107
|
+
"application/x-rar-compressed": "archive",
|
|
108
|
+
"application/vnd.rar": "archive",
|
|
109
|
+
"application/x-7z-compressed": "archive",
|
|
110
|
+
"application/x-tar": "archive",
|
|
111
|
+
"application/gzip": "archive",
|
|
112
|
+
"application/x-gzip": "archive",
|
|
113
|
+
"application/x-bzip2": "archive",
|
|
114
|
+
// Code
|
|
115
|
+
"application/json": "code",
|
|
116
|
+
"application/xml": "code",
|
|
117
|
+
"text/xml": "code",
|
|
118
|
+
"text/html": "code",
|
|
119
|
+
"text/css": "code",
|
|
120
|
+
"text/javascript": "code",
|
|
121
|
+
"application/javascript": "code",
|
|
122
|
+
"text/x-python": "code",
|
|
123
|
+
"text/x-java-source": "code",
|
|
124
|
+
"text/x-c": "code",
|
|
125
|
+
"text/x-yaml": "code",
|
|
126
|
+
"application/x-yaml": "code",
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Extension to category mapping
|
|
131
|
+
*/
|
|
132
|
+
const CATEGORY_BY_EXTENSION: Record<string, FileCategory> = {
|
|
133
|
+
// Images
|
|
134
|
+
".jpg": "image",
|
|
135
|
+
".jpeg": "image",
|
|
136
|
+
".png": "image",
|
|
137
|
+
".gif": "image",
|
|
138
|
+
".webp": "image",
|
|
139
|
+
".bmp": "image",
|
|
140
|
+
// Audio
|
|
141
|
+
".mp3": "audio",
|
|
142
|
+
".wav": "audio",
|
|
143
|
+
".ogg": "audio",
|
|
144
|
+
".m4a": "audio",
|
|
145
|
+
".amr": "audio",
|
|
146
|
+
// Video
|
|
147
|
+
".mp4": "video",
|
|
148
|
+
".mov": "video",
|
|
149
|
+
".avi": "video",
|
|
150
|
+
".mkv": "video",
|
|
151
|
+
".webm": "video",
|
|
152
|
+
// Documents
|
|
153
|
+
".pdf": "document",
|
|
154
|
+
".doc": "document",
|
|
155
|
+
".docx": "document",
|
|
156
|
+
".txt": "document",
|
|
157
|
+
".md": "document",
|
|
158
|
+
".rtf": "document",
|
|
159
|
+
".odt": "document",
|
|
160
|
+
".xls": "document",
|
|
161
|
+
".xlsx": "document",
|
|
162
|
+
".csv": "document",
|
|
163
|
+
".ods": "document",
|
|
164
|
+
".ppt": "document",
|
|
165
|
+
".pptx": "document",
|
|
166
|
+
// Archives
|
|
167
|
+
".zip": "archive",
|
|
168
|
+
".rar": "archive",
|
|
169
|
+
".7z": "archive",
|
|
170
|
+
".tar": "archive",
|
|
171
|
+
".gz": "archive",
|
|
172
|
+
".bz2": "archive",
|
|
173
|
+
// Code
|
|
174
|
+
".py": "code",
|
|
175
|
+
".js": "code",
|
|
176
|
+
".ts": "code",
|
|
177
|
+
".jsx": "code",
|
|
178
|
+
".tsx": "code",
|
|
179
|
+
".java": "code",
|
|
180
|
+
".cpp": "code",
|
|
181
|
+
".c": "code",
|
|
182
|
+
".go": "code",
|
|
183
|
+
".rs": "code",
|
|
184
|
+
".json": "code",
|
|
185
|
+
".xml": "code",
|
|
186
|
+
".yaml": "code",
|
|
187
|
+
".yml": "code",
|
|
188
|
+
".html": "code",
|
|
189
|
+
".css": "code",
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Extract file extension from a file name
|
|
194
|
+
* @param fileName - The file name to extract extension from
|
|
195
|
+
* @returns The extension with leading dot (e.g., ".jpg") or empty string if none
|
|
196
|
+
*/
|
|
197
|
+
function extractExtension(fileName: string): string {
|
|
198
|
+
const lastDot = fileName.lastIndexOf(".");
|
|
199
|
+
if (lastDot === -1 || lastDot === fileName.length - 1) {
|
|
200
|
+
return "";
|
|
201
|
+
}
|
|
202
|
+
return fileName.slice(lastDot).toLowerCase();
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Categorize a file based on MIME type and extension
|
|
207
|
+
*
|
|
208
|
+
* Priority:
|
|
209
|
+
* 1. Check MIME type prefix (image/, audio/, video/)
|
|
210
|
+
* 2. Check exact MIME type mapping (document, archive, code)
|
|
211
|
+
* 3. Check file extension from fileName
|
|
212
|
+
* 4. Return 'other' if no match
|
|
213
|
+
*
|
|
214
|
+
* @param contentType - MIME type string
|
|
215
|
+
* @param fileName - Optional file name for extension-based fallback
|
|
216
|
+
* @returns File category
|
|
217
|
+
*/
|
|
218
|
+
export function resolveFileCategory(
|
|
219
|
+
contentType: string,
|
|
220
|
+
fileName?: string
|
|
221
|
+
): FileCategory {
|
|
222
|
+
// Normalize content type (remove parameters like charset)
|
|
223
|
+
const mimeType = contentType.split(";")[0].trim().toLowerCase();
|
|
224
|
+
|
|
225
|
+
// Check MIME type prefix first (image/, audio/, video/)
|
|
226
|
+
if (mimeType.startsWith("image/")) {
|
|
227
|
+
return "image";
|
|
228
|
+
}
|
|
229
|
+
if (mimeType.startsWith("audio/")) {
|
|
230
|
+
return "audio";
|
|
231
|
+
}
|
|
232
|
+
if (mimeType.startsWith("video/")) {
|
|
233
|
+
return "video";
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// Check exact MIME type mapping (document, archive, code)
|
|
237
|
+
if (mimeType in CATEGORY_BY_MIME) {
|
|
238
|
+
return CATEGORY_BY_MIME[mimeType];
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// Check file extension if fileName is provided
|
|
242
|
+
if (fileName) {
|
|
243
|
+
const ext = extractExtension(fileName);
|
|
244
|
+
if (ext && ext in CATEGORY_BY_EXTENSION) {
|
|
245
|
+
return CATEGORY_BY_EXTENSION[ext];
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
return "other";
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Resolve file extension from MIME type or fileName
|
|
254
|
+
*
|
|
255
|
+
* Priority:
|
|
256
|
+
* 1. fileName extension (if provided and has extension)
|
|
257
|
+
* 2. MIME type mapping
|
|
258
|
+
* 3. ".bin" default
|
|
259
|
+
*
|
|
260
|
+
* @param contentType - MIME type string
|
|
261
|
+
* @param fileName - Optional file name to extract extension from (takes precedence)
|
|
262
|
+
* @returns Extension with leading dot (e.g., ".jpg") or ".bin" if unknown
|
|
263
|
+
*/
|
|
264
|
+
export function resolveExtension(
|
|
265
|
+
contentType: string,
|
|
266
|
+
fileName?: string
|
|
267
|
+
): string {
|
|
268
|
+
// Priority 1: fileName extension
|
|
269
|
+
if (fileName) {
|
|
270
|
+
const ext = extractExtension(fileName);
|
|
271
|
+
if (ext) {
|
|
272
|
+
return ext;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// Priority 2: MIME type mapping
|
|
277
|
+
const mimeType = contentType.split(";")[0].trim().toLowerCase();
|
|
278
|
+
if (mimeType in MIME_TO_EXTENSION) {
|
|
279
|
+
return MIME_TO_EXTENSION[mimeType];
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// Priority 3: Default
|
|
283
|
+
return ".bin";
|
|
284
|
+
}
|
package/src/index.ts
CHANGED
package/src/logger/index.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export * from "./logger.js";
|
|
1
|
+
export * from "./logger.js";
|
package/src/logger/logger.ts
CHANGED
|
@@ -1,51 +1,51 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 通用日志工具
|
|
3
|
-
*
|
|
4
|
-
* 提供分级日志功能:
|
|
5
|
-
* - info: 关键业务日志(默认显示)
|
|
6
|
-
* - debug: 调试日志(带 [DEBUG] 标记)
|
|
7
|
-
* - error: 错误日志
|
|
8
|
-
* - warn: 警告日志
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
export type LogLevel = "debug" | "info" | "warn" | "error";
|
|
12
|
-
|
|
13
|
-
export interface Logger {
|
|
14
|
-
debug: (msg: string) => void;
|
|
15
|
-
info: (msg: string) => void;
|
|
16
|
-
warn: (msg: string) => void;
|
|
17
|
-
error: (msg: string) => void;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export interface LoggerOptions {
|
|
21
|
-
log?: (msg: string) => void;
|
|
22
|
-
error?: (msg: string) => void;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* 创建带前缀的日志器
|
|
27
|
-
*
|
|
28
|
-
* @param prefix 日志前缀(如 "dingtalk", "feishu")
|
|
29
|
-
* @param opts 可选的日志输出函数
|
|
30
|
-
* @returns Logger 实例
|
|
31
|
-
*
|
|
32
|
-
* @example
|
|
33
|
-
* ```ts
|
|
34
|
-
* const logger = createLogger("dingtalk");
|
|
35
|
-
* logger.debug("connecting..."); // [dingtalk] [DEBUG] connecting...
|
|
36
|
-
* logger.info("connected"); // [dingtalk] connected
|
|
37
|
-
* logger.warn("slow response"); // [dingtalk] [WARN] slow response
|
|
38
|
-
* logger.error("failed"); // [dingtalk] [ERROR] failed
|
|
39
|
-
* ```
|
|
40
|
-
*/
|
|
41
|
-
export function createLogger(prefix: string, opts?: LoggerOptions): Logger {
|
|
42
|
-
const logFn = opts?.log ?? console.log;
|
|
43
|
-
const errorFn = opts?.error ?? console.error;
|
|
44
|
-
|
|
45
|
-
return {
|
|
46
|
-
debug: (msg: string) => logFn(`[${prefix}] [DEBUG] ${msg}`),
|
|
47
|
-
info: (msg: string) => logFn(`[${prefix}] ${msg}`),
|
|
48
|
-
warn: (msg: string) => logFn(`[${prefix}] [WARN] ${msg}`),
|
|
49
|
-
error: (msg: string) => errorFn(`[${prefix}] [ERROR] ${msg}`),
|
|
50
|
-
};
|
|
51
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* 通用日志工具
|
|
3
|
+
*
|
|
4
|
+
* 提供分级日志功能:
|
|
5
|
+
* - info: 关键业务日志(默认显示)
|
|
6
|
+
* - debug: 调试日志(带 [DEBUG] 标记)
|
|
7
|
+
* - error: 错误日志
|
|
8
|
+
* - warn: 警告日志
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
export type LogLevel = "debug" | "info" | "warn" | "error";
|
|
12
|
+
|
|
13
|
+
export interface Logger {
|
|
14
|
+
debug: (msg: string) => void;
|
|
15
|
+
info: (msg: string) => void;
|
|
16
|
+
warn: (msg: string) => void;
|
|
17
|
+
error: (msg: string) => void;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface LoggerOptions {
|
|
21
|
+
log?: (msg: string) => void;
|
|
22
|
+
error?: (msg: string) => void;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* 创建带前缀的日志器
|
|
27
|
+
*
|
|
28
|
+
* @param prefix 日志前缀(如 "dingtalk", "feishu")
|
|
29
|
+
* @param opts 可选的日志输出函数
|
|
30
|
+
* @returns Logger 实例
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* ```ts
|
|
34
|
+
* const logger = createLogger("dingtalk");
|
|
35
|
+
* logger.debug("connecting..."); // [dingtalk] [DEBUG] connecting...
|
|
36
|
+
* logger.info("connected"); // [dingtalk] connected
|
|
37
|
+
* logger.warn("slow response"); // [dingtalk] [WARN] slow response
|
|
38
|
+
* logger.error("failed"); // [dingtalk] [ERROR] failed
|
|
39
|
+
* ```
|
|
40
|
+
*/
|
|
41
|
+
export function createLogger(prefix: string, opts?: LoggerOptions): Logger {
|
|
42
|
+
const logFn = opts?.log ?? console.log;
|
|
43
|
+
const errorFn = opts?.error ?? console.error;
|
|
44
|
+
|
|
45
|
+
return {
|
|
46
|
+
debug: (msg: string) => logFn(`[${prefix}] [DEBUG] ${msg}`),
|
|
47
|
+
info: (msg: string) => logFn(`[${prefix}] ${msg}`),
|
|
48
|
+
warn: (msg: string) => logFn(`[${prefix}] [WARN] ${msg}`),
|
|
49
|
+
error: (msg: string) => errorFn(`[${prefix}] [ERROR] ${msg}`),
|
|
50
|
+
};
|
|
51
|
+
}
|