@dyno181cm.nexsoft/zentao_mcp 1.2.12 → 1.2.14
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/build/tools.js +20 -28
- package/package.json +1 -1
package/build/tools.js
CHANGED
|
@@ -53,45 +53,39 @@ function toFileUrl(filePath) {
|
|
|
53
53
|
}
|
|
54
54
|
return `file:///${normalized}`;
|
|
55
55
|
}
|
|
56
|
-
/** Convert a local file to a Base64 data URI for safe rendering in browser/chat UI. */
|
|
57
|
-
function fileToBase64(filePath) {
|
|
58
|
-
try {
|
|
59
|
-
const data = fs.readFileSync(filePath);
|
|
60
|
-
const ext = path.extname(filePath).toLowerCase().replace('.', '');
|
|
61
|
-
const mime = ext === 'svg' ? 'image/svg+xml' : `image/${ext || 'png'}`;
|
|
62
|
-
return `data:${mime};base64,${data.toString('base64')}`;
|
|
63
|
-
}
|
|
64
|
-
catch {
|
|
65
|
-
return toFileUrl(filePath); // fallback to file URL if read fails
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
56
|
/**
|
|
69
|
-
* Find all <img src="...">
|
|
57
|
+
* Find all <img src="..."> tags in an HTML string, download each image
|
|
70
58
|
* to the local tmp directory using the authenticated ZenTao client,
|
|
71
|
-
* and replace the
|
|
72
|
-
*
|
|
59
|
+
* and replace the entire <img> tag with an <a> link pointing to the local file:// URL.
|
|
60
|
+
* This saves AI tokens (no huge Base64 data) and avoids local file load errors in UI.
|
|
73
61
|
*/
|
|
74
62
|
async function localizeImages(html) {
|
|
75
63
|
if (!html)
|
|
76
64
|
return html;
|
|
77
|
-
const
|
|
78
|
-
const matches = [...html.matchAll(
|
|
65
|
+
const imgTagRegex = /<img[^>]+src=["']([^"']+)["'][^>]*>/gi;
|
|
66
|
+
const matches = [...html.matchAll(imgTagRegex)];
|
|
79
67
|
if (matches.length === 0)
|
|
80
68
|
return html;
|
|
81
69
|
let result = html;
|
|
82
70
|
await Promise.all(matches.map(async (match) => {
|
|
71
|
+
const fullTag = match[0];
|
|
83
72
|
const remoteUrl = match[1];
|
|
84
|
-
|
|
85
|
-
const filename =
|
|
86
|
-
const
|
|
73
|
+
const altMatch = fullTag.match(/alt=["']([^"']+)["']/i);
|
|
74
|
+
const filename = path.basename(remoteUrl.split('?')[0]);
|
|
75
|
+
const linkText = altMatch ? altMatch[1] : filename;
|
|
76
|
+
const localFilename = `zentao_img_${filename}`;
|
|
77
|
+
const localPath = path.join(os.tmpdir(), localFilename);
|
|
87
78
|
try {
|
|
88
79
|
if (!fs.existsSync(localPath)) {
|
|
89
80
|
await client.downloadImageToLocal(remoteUrl, localPath);
|
|
90
81
|
}
|
|
91
|
-
|
|
82
|
+
const fileUrl = toFileUrl(localPath);
|
|
83
|
+
// Replace img tag with a standard hyperlink (no inline rendering, saves tokens)
|
|
84
|
+
const linkTag = `<a href="${fileUrl}">📎 ${linkText}</a>`;
|
|
85
|
+
result = result.split(fullTag).join(linkTag);
|
|
92
86
|
}
|
|
93
87
|
catch {
|
|
94
|
-
//
|
|
88
|
+
// Keep original tag if download fails
|
|
95
89
|
}
|
|
96
90
|
}));
|
|
97
91
|
return result;
|
|
@@ -144,10 +138,6 @@ async function renderAttachments(files) {
|
|
|
144
138
|
const lines = enriched.map((f) => {
|
|
145
139
|
if (!f.localPath)
|
|
146
140
|
return `- 📎 ${f.title} — *(download failed, Size: ${formatSize(f.size)})*`;
|
|
147
|
-
if (IMAGE_EXTS.has(f.ext)) {
|
|
148
|
-
const base64 = fileToBase64(f.localPath);
|
|
149
|
-
return `- 📎  *(Size: ${formatSize(f.size)})*`;
|
|
150
|
-
}
|
|
151
141
|
const fileUrl = toFileUrl(f.localPath);
|
|
152
142
|
return `- 📎 [${f.title}](${fileUrl}) *(Size: ${formatSize(f.size)})*`;
|
|
153
143
|
});
|
|
@@ -183,10 +173,11 @@ export async function taskToMarkdown(rawTask) {
|
|
|
183
173
|
const localizedDesc = await localizeImages(rawTask.desc);
|
|
184
174
|
const desc = htmlToMarkdown(localizedDesc);
|
|
185
175
|
const attachments = await renderAttachments(parseFiles(rawTask.files));
|
|
176
|
+
const metaList = meta.map(m => `- ${m}`).join('\n');
|
|
186
177
|
return [
|
|
187
178
|
`# Task #${rawTask.id}: ${rawTask.name}`,
|
|
188
179
|
'',
|
|
189
|
-
|
|
180
|
+
metaList,
|
|
190
181
|
'',
|
|
191
182
|
'## Description',
|
|
192
183
|
desc || '*No description provided.*',
|
|
@@ -219,10 +210,11 @@ export async function bugToMarkdown(rawBug) {
|
|
|
219
210
|
const localizedSteps = await localizeImages(rawBug.steps);
|
|
220
211
|
const steps = htmlToMarkdown(localizedSteps);
|
|
221
212
|
const attachments = await renderAttachments(parseFiles(rawBug.files));
|
|
213
|
+
const metaList = meta.map(m => `- ${m}`).join('\n');
|
|
222
214
|
return [
|
|
223
215
|
`# Bug #${rawBug.id}: ${rawBug.title}`,
|
|
224
216
|
'',
|
|
225
|
-
|
|
217
|
+
metaList,
|
|
226
218
|
'',
|
|
227
219
|
'## Repro Steps',
|
|
228
220
|
steps || '*No steps provided.*',
|