@neosamon/jira-mcp-server 0.2.0 → 0.2.1
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/index.js +277 -2
- package/package.json +1 -1
package/build/index.js
CHANGED
|
@@ -105,7 +105,254 @@ async function addComment(config, issueKey, body) {
|
|
|
105
105
|
body: JSON.stringify({ body }),
|
|
106
106
|
});
|
|
107
107
|
}
|
|
108
|
-
|
|
108
|
+
/**
|
|
109
|
+
* 从对象中按嵌套路径提取值
|
|
110
|
+
* 支持点分隔的路径字符串,如 "inwardIssue.key"
|
|
111
|
+
* @param obj - 要提取的对象
|
|
112
|
+
* @param path - 路径数组(如 ["inwardIssue", "key"])或点分隔字符串
|
|
113
|
+
* @returns 提取的值,如果路径无效则返回 null
|
|
114
|
+
*/
|
|
115
|
+
function extractByPath(obj, path) {
|
|
116
|
+
if (obj == null || typeof obj !== 'object') {
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
const parts = Array.isArray(path) ? path : path.split('.');
|
|
120
|
+
let current = obj;
|
|
121
|
+
for (const part of parts) {
|
|
122
|
+
if (current == null || typeof current !== 'object') {
|
|
123
|
+
return null;
|
|
124
|
+
}
|
|
125
|
+
current = current[part];
|
|
126
|
+
}
|
|
127
|
+
return current;
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* 从对象中按规则提取值
|
|
131
|
+
* 支持自定义字段顺序和格式化
|
|
132
|
+
* @param obj - 要提取的对象
|
|
133
|
+
* @param rule - 提取规则
|
|
134
|
+
* @returns 格式化后的字符串,如果无法提取则返回 null
|
|
135
|
+
*/
|
|
136
|
+
function extractByRule(obj, rule) {
|
|
137
|
+
// 检查所有必需字段
|
|
138
|
+
for (const field of rule.fields) {
|
|
139
|
+
if (field.required) {
|
|
140
|
+
const value = extractByPath(obj, field.path);
|
|
141
|
+
if (value == null) {
|
|
142
|
+
return null; // 必需字段不存在,规则匹配失败
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
// 收集所有字段值(按顺序)
|
|
147
|
+
const parts = [];
|
|
148
|
+
for (let i = 0; i < rule.fields.length; i++) {
|
|
149
|
+
const field = rule.fields[i];
|
|
150
|
+
const value = extractByPath(obj, field.path);
|
|
151
|
+
// 跳过不存在的可选字段
|
|
152
|
+
if (value == null) {
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
// 空值处理
|
|
156
|
+
if (value === '') {
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
// 数字转字符串
|
|
160
|
+
let part = typeof value === 'string' ? value : String(value);
|
|
161
|
+
// 应用长度限制
|
|
162
|
+
if (field.maxLength && part.length > field.maxLength) {
|
|
163
|
+
part = part.substring(0, field.maxLength) + '...';
|
|
164
|
+
}
|
|
165
|
+
// 添加前缀
|
|
166
|
+
if (field.prefix) {
|
|
167
|
+
part = field.prefix + part;
|
|
168
|
+
}
|
|
169
|
+
// 添加后缀
|
|
170
|
+
if (field.suffix) {
|
|
171
|
+
part = part + field.suffix;
|
|
172
|
+
}
|
|
173
|
+
// 添加分隔符(连接到下一个字段)
|
|
174
|
+
if (field.separator && i < rule.fields.length - 1) {
|
|
175
|
+
// 检查下一个字段是否存在
|
|
176
|
+
const nextField = rule.fields[i + 1];
|
|
177
|
+
const nextValue = extractByPath(obj, nextField.path);
|
|
178
|
+
if (nextValue != null && nextValue !== '') {
|
|
179
|
+
part = part + field.separator;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
parts.push(part);
|
|
183
|
+
}
|
|
184
|
+
if (parts.length === 0) {
|
|
185
|
+
return null;
|
|
186
|
+
}
|
|
187
|
+
// 组合所有部分
|
|
188
|
+
let result = parts.join('');
|
|
189
|
+
// 添加整体前缀后缀
|
|
190
|
+
if (rule.prefix) {
|
|
191
|
+
result = rule.prefix + result;
|
|
192
|
+
}
|
|
193
|
+
if (rule.suffix) {
|
|
194
|
+
result = result + rule.suffix;
|
|
195
|
+
}
|
|
196
|
+
return result;
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* 提取规则配置
|
|
200
|
+
* 按优先级顺序尝试提取,第一个成功的规则被使用
|
|
201
|
+
*/
|
|
202
|
+
const extractionRules = [
|
|
203
|
+
// ===== 第一优先级:简单字段 =====
|
|
204
|
+
{
|
|
205
|
+
fields: [{ path: 'name', required: true }]
|
|
206
|
+
},
|
|
207
|
+
{
|
|
208
|
+
fields: [{ path: 'value', required: true }]
|
|
209
|
+
},
|
|
210
|
+
{
|
|
211
|
+
fields: [{ path: 'content', required: true }]
|
|
212
|
+
},
|
|
213
|
+
{
|
|
214
|
+
fields: [{ path: 'body', required: true }]
|
|
215
|
+
},
|
|
216
|
+
{
|
|
217
|
+
fields: [{ path: 'summary', required: true }]
|
|
218
|
+
},
|
|
219
|
+
{
|
|
220
|
+
fields: [{ path: 'title', required: true }]
|
|
221
|
+
},
|
|
222
|
+
{
|
|
223
|
+
fields: [{ path: 'key', required: true }]
|
|
224
|
+
},
|
|
225
|
+
// ===== 第二优先级:组合字段 - issuelinks (inward + outward + summary) =====
|
|
226
|
+
{
|
|
227
|
+
fields: [
|
|
228
|
+
{ path: 'inwardIssue.key', required: true, prefix: '→ ', separator: ' - ' },
|
|
229
|
+
{ path: 'inwardIssue.fields.summary', required: false, separator: ' ', maxLength: 50 },
|
|
230
|
+
{ path: ['type', 'name'], required: false, prefix: ' [', suffix: ']' }
|
|
231
|
+
]
|
|
232
|
+
},
|
|
233
|
+
// ===== 第三优先级:组合字段 - issuelinks (outward + summary) =====
|
|
234
|
+
{
|
|
235
|
+
fields: [
|
|
236
|
+
{ path: 'outwardIssue.key', required: true, prefix: '← ', separator: ' - ' },
|
|
237
|
+
{ path: 'outwardIssue.fields.summary', required: false, separator: ' ', maxLength: 50 },
|
|
238
|
+
{ path: ['type', 'name'], required: false, prefix: ' [', suffix: ']' }
|
|
239
|
+
]
|
|
240
|
+
},
|
|
241
|
+
// ===== 第四优先级:组合字段 - issuelinks (仅 inward) =====
|
|
242
|
+
{
|
|
243
|
+
fields: [
|
|
244
|
+
{ path: 'inwardIssue.key', required: true, prefix: '→ ' },
|
|
245
|
+
{ path: ['type', 'name'], required: false, prefix: ' [', suffix: ']' }
|
|
246
|
+
]
|
|
247
|
+
},
|
|
248
|
+
// ===== 第五优先级:组合字段 - issuelinks (仅 outward) =====
|
|
249
|
+
{
|
|
250
|
+
fields: [
|
|
251
|
+
{ path: 'outwardIssue.key', required: true, prefix: '← ' },
|
|
252
|
+
{ path: ['type', 'name'], required: false, prefix: ' [', suffix: ']' }
|
|
253
|
+
]
|
|
254
|
+
},
|
|
255
|
+
// ===== 第六优先级:组合字段简化版 =====
|
|
256
|
+
{
|
|
257
|
+
fields: [
|
|
258
|
+
{ path: 'key', required: true },
|
|
259
|
+
{ path: 'fields.summary', required: false, separator: ' - ', maxLength: 30 }
|
|
260
|
+
]
|
|
261
|
+
},
|
|
262
|
+
// ===== 第七优先级:组合字段 - 仅 type =====
|
|
263
|
+
{
|
|
264
|
+
fields: [
|
|
265
|
+
{ path: ['type', 'name'], required: true, prefix: ' [', suffix: ']' }
|
|
266
|
+
]
|
|
267
|
+
},
|
|
268
|
+
// ===== 第八优先级:附件信息 =====
|
|
269
|
+
{
|
|
270
|
+
fields: [
|
|
271
|
+
{ path: 'filename', required: false },
|
|
272
|
+
{ path: 'mimeType', required: false, prefix: ' [', suffix: ']' },
|
|
273
|
+
{ path: 'size', required: false, prefix: '(', suffix: ' bytes)' }
|
|
274
|
+
]
|
|
275
|
+
},
|
|
276
|
+
];
|
|
277
|
+
/**
|
|
278
|
+
* 从对象中按优先级提取可显示的字符串值
|
|
279
|
+
* 支持简单模式和组合模式
|
|
280
|
+
* @param obj - 要提取的对象
|
|
281
|
+
* @returns 提取的字符串值,如果找不到则返回 null
|
|
282
|
+
*/
|
|
283
|
+
function extractPropertyValue(obj) {
|
|
284
|
+
for (const rule of extractionRules) {
|
|
285
|
+
const result = extractByRule(obj, rule);
|
|
286
|
+
if (result !== null) {
|
|
287
|
+
return result;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
return null;
|
|
291
|
+
}
|
|
292
|
+
/**
|
|
293
|
+
* 格式化数组项为字符串,使用组合长度限制策略
|
|
294
|
+
* @param items - 数组项
|
|
295
|
+
* @param extractor - 从项中提取字符串的函数
|
|
296
|
+
* @param maxLength - 最大字符长度(默认 80)
|
|
297
|
+
* @returns 格式化后的字符串
|
|
298
|
+
*/
|
|
299
|
+
function formatArrayItems(items, extractor, maxLength = 80) {
|
|
300
|
+
// 空数组
|
|
301
|
+
if (items.length === 0) {
|
|
302
|
+
return "[]";
|
|
303
|
+
}
|
|
304
|
+
const parts = [];
|
|
305
|
+
let currentLength = 0;
|
|
306
|
+
const minItemsToShow = Math.min(2, items.length); // 至少显示 2 个元素(如果有的话)
|
|
307
|
+
for (let i = 0; i < items.length; i++) {
|
|
308
|
+
const extracted = extractor(items[i], i);
|
|
309
|
+
// 如果提取失败,停止处理
|
|
310
|
+
if (extracted === null) {
|
|
311
|
+
break;
|
|
312
|
+
}
|
|
313
|
+
const separator = parts.length > 0 ? ", " : "";
|
|
314
|
+
const newLength = currentLength + separator.length + extracted.length;
|
|
315
|
+
// 组合策略:确保至少显示 minItemsToShow 个元素,但不超过 maxLength
|
|
316
|
+
if (newLength > maxLength && parts.length >= minItemsToShow) {
|
|
317
|
+
// 超过限制且已显示足够元素,添加省略标记和总数
|
|
318
|
+
return `${parts.join(", ")}... (${items.length} total)`;
|
|
319
|
+
}
|
|
320
|
+
parts.push(extracted);
|
|
321
|
+
currentLength = newLength;
|
|
322
|
+
}
|
|
323
|
+
return parts.join(", ");
|
|
324
|
+
}
|
|
325
|
+
/**
|
|
326
|
+
* 尝试对单个对象应用规则列表
|
|
327
|
+
* @param obj - 要提取的对象
|
|
328
|
+
* @returns 格式化后的字符串,如果无法提取则返回 null
|
|
329
|
+
*/
|
|
330
|
+
function extractByRules(obj) {
|
|
331
|
+
for (const rule of extractionRules) {
|
|
332
|
+
const result = extractByRule(obj, rule);
|
|
333
|
+
if (result !== null) {
|
|
334
|
+
return result;
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
return null;
|
|
338
|
+
}
|
|
339
|
+
/**
|
|
340
|
+
* 格式化普通值(非对象)为字符串
|
|
341
|
+
* @param value - 值
|
|
342
|
+
* @returns 字符串表示
|
|
343
|
+
*/
|
|
344
|
+
function formatPrimitiveValue(value) {
|
|
345
|
+
if (value === null || value === undefined) {
|
|
346
|
+
return "(null)";
|
|
347
|
+
}
|
|
348
|
+
if (typeof value === 'string') {
|
|
349
|
+
return value;
|
|
350
|
+
}
|
|
351
|
+
if (typeof value === 'number' || typeof value === 'boolean') {
|
|
352
|
+
return String(value);
|
|
353
|
+
}
|
|
354
|
+
return "(unknown)";
|
|
355
|
+
}
|
|
109
356
|
/**
|
|
110
357
|
* 格式化字段值用于显示
|
|
111
358
|
* 根据值的类型采用不同的展示策略
|
|
@@ -122,8 +369,36 @@ function formatFieldValue(value) {
|
|
|
122
369
|
}
|
|
123
370
|
return `${value.substring(0, 50)}...`;
|
|
124
371
|
}
|
|
125
|
-
// 数组类型
|
|
372
|
+
// 数组类型 - 智能展开数组内容
|
|
126
373
|
if (Array.isArray(value)) {
|
|
374
|
+
// 空数组
|
|
375
|
+
if (value.length === 0) {
|
|
376
|
+
return "[]";
|
|
377
|
+
}
|
|
378
|
+
// 检查数组元素类型
|
|
379
|
+
const hasObjects = value.some(item => typeof item === 'object' && item !== null);
|
|
380
|
+
const hasPrimitives = value.some(item => typeof item !== 'object');
|
|
381
|
+
// 纯普通类型数组 - 转字符串连接
|
|
382
|
+
if (!hasObjects) {
|
|
383
|
+
return formatArrayItems(value, (item) => formatPrimitiveValue(item));
|
|
384
|
+
}
|
|
385
|
+
// 对象数组 - 逐项规则匹配
|
|
386
|
+
if (!hasPrimitives) {
|
|
387
|
+
return formatArrayItems(value, (item) => extractByRules(item));
|
|
388
|
+
}
|
|
389
|
+
// 混合类型数组 - 分别处理
|
|
390
|
+
if (hasObjects && hasPrimitives) {
|
|
391
|
+
return formatArrayItems(value, (item) => {
|
|
392
|
+
if (typeof item === 'object' && item !== null) {
|
|
393
|
+
const result = extractByRules(item);
|
|
394
|
+
return result !== null ? result : `[Object]`;
|
|
395
|
+
}
|
|
396
|
+
else {
|
|
397
|
+
return formatPrimitiveValue(item);
|
|
398
|
+
}
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
// 其他情况 - 回退到摘要
|
|
127
402
|
return `[Array] (${value.length} items)`;
|
|
128
403
|
}
|
|
129
404
|
// 对象类型
|