@stephen1620/dsh-tomato-board 0.1.0 → 0.2.0
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/README.md +44 -14
- package/cordis.patch.yml +1 -1
- package/lib/client.js +1219 -184
- package/lib/client.js.map +1 -1
- package/lib/index.js +311 -15
- package/lib/types/client/StoryPoints.d.ts +7 -0
- package/lib/types/client/TomatoBoard.d.ts +13 -0
- package/lib/types/index.d.ts +4 -0
- package/package.json +2 -2
package/lib/index.js
CHANGED
|
@@ -7,13 +7,17 @@ const ROUTE = "/api/tomato-board/items";
|
|
|
7
7
|
const OPEN_ROUTE = "/api/tomato-board/open";
|
|
8
8
|
const TRANSITIONS_ROUTE = "/api/tomato-board/transitions";
|
|
9
9
|
const TRANSITION_ROUTE = "/api/tomato-board/transition";
|
|
10
|
-
const
|
|
10
|
+
const FILTERS_ROUTE = "/api/tomato-board/filters";
|
|
11
|
+
const DEFAULT_PRIORITY_NAMES = new Map([
|
|
11
12
|
["69e65065-4b34-4109-bca9-0154e548554a", "P0"],
|
|
12
13
|
["8f7912a5-9176-4a79-a269-2269ac42b5a2", "P1"],
|
|
13
14
|
["ca8c3e43-3e7b-444d-8940-d0967d944921", "P2"],
|
|
15
|
+
["ec31e4c1-b55b-479d-be97-86d5f7bf38ef", "P2"],
|
|
14
16
|
["1a3e1092-7d70-42ee-ad38-0e8d953c4c23", "P3"],
|
|
15
17
|
["faae52da-28c8-46fc-96dd-db9cdb28b557", "P4"]
|
|
16
18
|
]);
|
|
19
|
+
let itemsCache = null;
|
|
20
|
+
let itemsInFlight = null;
|
|
17
21
|
function firstText(value) {
|
|
18
22
|
if (typeof value === "string") return value.trim();
|
|
19
23
|
if (Array.isArray(value)) return value.map(firstText).find(Boolean) ?? "";
|
|
@@ -33,6 +37,9 @@ function firstText(value) {
|
|
|
33
37
|
}
|
|
34
38
|
return "";
|
|
35
39
|
}
|
|
40
|
+
function textList(value) {
|
|
41
|
+
return [...new Set((Array.isArray(value) ? value : value ? [value] : []).map(firstText).filter(Boolean))];
|
|
42
|
+
}
|
|
36
43
|
function unwrapItems(payload) {
|
|
37
44
|
if (Array.isArray(payload)) return payload;
|
|
38
45
|
if (!payload || typeof payload !== "object") return [];
|
|
@@ -63,19 +70,26 @@ function unwrapTransitions(payload) {
|
|
|
63
70
|
}
|
|
64
71
|
return [];
|
|
65
72
|
}
|
|
66
|
-
function normalizeItem(raw) {
|
|
73
|
+
function normalizeItem(raw, priorityNames = DEFAULT_PRIORITY_NAMES) {
|
|
67
74
|
const item = raw && typeof raw === "object" ? raw : {};
|
|
68
75
|
const nested = item.value && typeof item.value === "object" ? item.value : item.values && typeof item.values === "object" ? item.values : item;
|
|
76
|
+
const workspaceValue = item.workspace ?? nested.workspace ?? nested["所属空间"];
|
|
77
|
+
const workspace = workspaceValue && typeof workspaceValue === "object" ? workspaceValue : {};
|
|
78
|
+
const workspaceKey = firstText(workspace.key);
|
|
79
|
+
const workspaceName = firstText(workspace.name ?? workspaceValue);
|
|
69
80
|
return {
|
|
70
81
|
itemKey: firstText(item.itemKey ?? item.key ?? item.id ?? nested.itemKey ?? nested.key),
|
|
71
82
|
title: firstText(item.title ?? item.name ?? nested.title ?? nested.name ?? nested["标题"]),
|
|
72
83
|
status: firstText(item.status ?? nested.status ?? nested["状态"]),
|
|
73
84
|
itemType: firstText(item.itemType ?? item.type ?? nested.itemType ?? nested["类型"]),
|
|
74
|
-
workspace:
|
|
85
|
+
workspace: workspaceKey || workspaceName,
|
|
86
|
+
workspaceKey,
|
|
87
|
+
workspaceName,
|
|
75
88
|
creator: firstText(item.creator ?? item.createdBy ?? nested["创建人"] ?? nested.createdBy),
|
|
89
|
+
assignees: textList(item.assignees ?? item.assignee ?? nested.assignees ?? nested.assignee ?? nested["负责人"]),
|
|
76
90
|
priority: (() => {
|
|
77
91
|
const priority = firstText(item.priority ?? nested.priority ?? nested["优先级"]);
|
|
78
|
-
return
|
|
92
|
+
return priorityNames.get(priority) ?? priority;
|
|
79
93
|
})()
|
|
80
94
|
};
|
|
81
95
|
}
|
|
@@ -166,12 +180,20 @@ function itemKeyFromRoute(requestUrl, route) {
|
|
|
166
180
|
const itemKey = decodeURIComponent(pathname.slice(route.length).replace(/^\/+/, "")).trim();
|
|
167
181
|
return /^[A-Za-z][A-Za-z0-9]*(?:-[A-Za-z0-9]+)*-\d+$/u.test(itemKey) ? itemKey : "";
|
|
168
182
|
}
|
|
169
|
-
|
|
183
|
+
function assigneeIql(config, assignee) {
|
|
184
|
+
const base = config.iql?.trim();
|
|
185
|
+
const clause = `负责人 in [${assignee === "currentUser()" ? "currentUser()" : JSON.stringify(assignee)}]`;
|
|
186
|
+
return base ? `(${base}) and ${clause}` : clause;
|
|
187
|
+
}
|
|
188
|
+
async function loadItems(config, assignee) {
|
|
170
189
|
const { executable, profile } = cliSettings(config);
|
|
171
|
-
const iql = config
|
|
190
|
+
const iql = assigneeIql(config, assignee);
|
|
191
|
+
const priorityNames = new Map([...DEFAULT_PRIORITY_NAMES, ...Object.entries(config.priorityNames ?? {})]);
|
|
172
192
|
const rawItems = [];
|
|
173
193
|
let page = 1;
|
|
174
|
-
|
|
194
|
+
let truncated = false;
|
|
195
|
+
const maxItems = Math.max(1, Math.min(config.maxItems ?? 5e3, 2e4));
|
|
196
|
+
while (rawItems.length < maxItems) {
|
|
175
197
|
const result = await execFileAsync(executable, [
|
|
176
198
|
"team",
|
|
177
199
|
"item",
|
|
@@ -185,7 +207,7 @@ async function loadItems(config) {
|
|
|
185
207
|
"--iql",
|
|
186
208
|
iql,
|
|
187
209
|
"--fields",
|
|
188
|
-
"priority,createdBy",
|
|
210
|
+
"priority,createdBy,assignee",
|
|
189
211
|
"--output",
|
|
190
212
|
"json"
|
|
191
213
|
], {
|
|
@@ -197,19 +219,208 @@ async function loadItems(config) {
|
|
|
197
219
|
rawItems.push(...pageItems);
|
|
198
220
|
const record = payload && typeof payload === "object" ? payload : {};
|
|
199
221
|
const total = Number(record.count ?? record.total ?? record.data?.count);
|
|
200
|
-
|
|
222
|
+
const hasMore = record.hasNext === true || record.hasMore === true || Number.isFinite(total) && rawItems.length < total || !Number.isFinite(total) && pageItems.length === 50;
|
|
223
|
+
if (rawItems.length >= maxItems && hasMore) {
|
|
224
|
+
truncated = true;
|
|
225
|
+
break;
|
|
226
|
+
}
|
|
227
|
+
if (!hasMore || pageItems.length === 0) break;
|
|
201
228
|
page += 1;
|
|
202
229
|
}
|
|
203
|
-
const excludedStatuses = new Set([
|
|
230
|
+
const excludedStatuses = new Set(config.excludedStatuses ?? [
|
|
204
231
|
"测试通过",
|
|
205
232
|
"测试完成",
|
|
206
233
|
"不修复",
|
|
207
234
|
"已取消"
|
|
208
235
|
]);
|
|
209
|
-
return
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
236
|
+
return {
|
|
237
|
+
items: rawItems.slice(0, maxItems).map((item) => normalizeItem(item, priorityNames)).filter((item) => item.itemKey && item.title && item.status && !excludedStatuses.has(item.status)).map((item) => ({
|
|
238
|
+
...item,
|
|
239
|
+
tomatoUrl: tomatoItemUrl(config, item.itemKey)
|
|
240
|
+
})),
|
|
241
|
+
truncated
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
function loadItemsCached(config, assignee) {
|
|
245
|
+
const ttl = Math.max(0, Math.min(config.cacheTtlMs ?? 15e3, 3e5));
|
|
246
|
+
const key = JSON.stringify([
|
|
247
|
+
cliSettings(config),
|
|
248
|
+
assignee,
|
|
249
|
+
config.iql,
|
|
250
|
+
config.maxItems,
|
|
251
|
+
config.excludedStatuses,
|
|
252
|
+
config.priorityNames,
|
|
253
|
+
config.tomatoOrigin,
|
|
254
|
+
config.tomatoTenant
|
|
255
|
+
]);
|
|
256
|
+
if (itemsCache?.key === key && itemsCache.expiresAt > Date.now()) return Promise.resolve(itemsCache.value);
|
|
257
|
+
if (itemsInFlight?.key === key) return itemsInFlight.promise;
|
|
258
|
+
const promise = loadItems(config, assignee).then((value) => {
|
|
259
|
+
itemsCache = {
|
|
260
|
+
key,
|
|
261
|
+
expiresAt: Date.now() + ttl,
|
|
262
|
+
value
|
|
263
|
+
};
|
|
264
|
+
return value;
|
|
265
|
+
}).finally(() => {
|
|
266
|
+
if (itemsInFlight?.promise === promise) itemsInFlight = null;
|
|
267
|
+
});
|
|
268
|
+
itemsInFlight = {
|
|
269
|
+
key,
|
|
270
|
+
promise
|
|
271
|
+
};
|
|
272
|
+
return promise;
|
|
273
|
+
}
|
|
274
|
+
async function loadFilters(config) {
|
|
275
|
+
const users = [];
|
|
276
|
+
const workspaces = [];
|
|
277
|
+
for (let page = 1; page <= 100; page += 1) {
|
|
278
|
+
const payload = await runJson(config, [
|
|
279
|
+
"team",
|
|
280
|
+
"user",
|
|
281
|
+
"list",
|
|
282
|
+
"--page",
|
|
283
|
+
String(page),
|
|
284
|
+
"--size",
|
|
285
|
+
"300"
|
|
286
|
+
]);
|
|
287
|
+
const record = payload && typeof payload === "object" ? payload : {};
|
|
288
|
+
const values = Array.isArray(record.users) ? record.users : [];
|
|
289
|
+
users.push(...values);
|
|
290
|
+
if (!record.nextPageIndex || values.length === 0) break;
|
|
291
|
+
}
|
|
292
|
+
for (let page = 1; page <= 100; page += 1) {
|
|
293
|
+
const payload = await runJson(config, [
|
|
294
|
+
"team",
|
|
295
|
+
"workspace",
|
|
296
|
+
"list",
|
|
297
|
+
"--page",
|
|
298
|
+
String(page),
|
|
299
|
+
"--size",
|
|
300
|
+
"100"
|
|
301
|
+
]);
|
|
302
|
+
const record = payload && typeof payload === "object" ? payload : {};
|
|
303
|
+
const values = Array.isArray(record.workspaces) ? record.workspaces : [];
|
|
304
|
+
workspaces.push(...values);
|
|
305
|
+
if (!record.nextPageIndex || values.length === 0) break;
|
|
306
|
+
}
|
|
307
|
+
return {
|
|
308
|
+
users: users.map((value) => {
|
|
309
|
+
const user = value && typeof value === "object" ? value : {};
|
|
310
|
+
return {
|
|
311
|
+
username: firstText(user.username),
|
|
312
|
+
name: firstText(user.nickname ?? user.name ?? user.username)
|
|
313
|
+
};
|
|
314
|
+
}).filter((user) => user.username),
|
|
315
|
+
workspaces: workspaces.map((value) => {
|
|
316
|
+
const workspace = value && typeof value === "object" ? value : {};
|
|
317
|
+
return {
|
|
318
|
+
key: firstText(workspace.key),
|
|
319
|
+
name: firstText(workspace.name ?? workspace.key)
|
|
320
|
+
};
|
|
321
|
+
}).filter((workspace) => workspace.key || workspace.name)
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
function fieldObjectIds(raw) {
|
|
325
|
+
return [...new Set((Array.isArray(raw) ? raw : raw ? [raw] : []).flatMap((value) => {
|
|
326
|
+
if (typeof value === "string") return [value.trim()];
|
|
327
|
+
if (!value || typeof value !== "object") return [];
|
|
328
|
+
const record = value;
|
|
329
|
+
return [
|
|
330
|
+
record.objectId,
|
|
331
|
+
record.value,
|
|
332
|
+
record.id
|
|
333
|
+
].map(firstText).filter(Boolean);
|
|
334
|
+
}).filter(Boolean))];
|
|
335
|
+
}
|
|
336
|
+
function storyPointValue(raw) {
|
|
337
|
+
if (raw === null || raw === void 0 || raw === "" || typeof raw === "boolean") return null;
|
|
338
|
+
if (typeof raw !== "number" && typeof raw !== "string") return null;
|
|
339
|
+
if (typeof raw === "string" && !raw.trim()) return null;
|
|
340
|
+
const value = Number(raw);
|
|
341
|
+
return Number.isFinite(value) && value >= 0 ? value : null;
|
|
342
|
+
}
|
|
343
|
+
async function loadSprints(config) {
|
|
344
|
+
const sprints = [];
|
|
345
|
+
for (let page = 1; page <= 100; page += 1) {
|
|
346
|
+
const payload = await runJson(config, [
|
|
347
|
+
"team",
|
|
348
|
+
"sprint",
|
|
349
|
+
"list",
|
|
350
|
+
"--status",
|
|
351
|
+
"active,notStarted,completed",
|
|
352
|
+
"--page",
|
|
353
|
+
String(page),
|
|
354
|
+
"--size",
|
|
355
|
+
"100"
|
|
356
|
+
]);
|
|
357
|
+
const values = Array.isArray(payload.sprints) ? payload.sprints : [];
|
|
358
|
+
sprints.push(...values);
|
|
359
|
+
if (!(Number(payload.nextPageIndex) > page) || !values.length) return { sprints };
|
|
360
|
+
}
|
|
361
|
+
throw new Error("迭代数量超出读取上限,请缩小查询范围");
|
|
362
|
+
}
|
|
363
|
+
async function loadStoryItems(config, sprint, assignee) {
|
|
364
|
+
const owner = assignee === "currentUser()" ? assignee : JSON.stringify(assignee);
|
|
365
|
+
const iql = `迭代 = ${JSON.stringify(sprint)} and 负责人 in [${owner}] and 类型 in [Story, EnablerStory, Task]`;
|
|
366
|
+
const items = /* @__PURE__ */ new Map();
|
|
367
|
+
const limit = Math.max(1, Math.min(config.maxItems ?? 5e3, 2e4));
|
|
368
|
+
for (let page = 1;; page += 1) {
|
|
369
|
+
const payload = await runJson(config, [
|
|
370
|
+
"team",
|
|
371
|
+
"item",
|
|
372
|
+
"search",
|
|
373
|
+
"--iql",
|
|
374
|
+
iql,
|
|
375
|
+
"--fields",
|
|
376
|
+
"StoryPoint,assignee,sprint",
|
|
377
|
+
"--page",
|
|
378
|
+
String(page),
|
|
379
|
+
"--size",
|
|
380
|
+
"50"
|
|
381
|
+
]);
|
|
382
|
+
const values = unwrapItems(payload);
|
|
383
|
+
for (const raw of values) {
|
|
384
|
+
const item = normalizeItem(raw);
|
|
385
|
+
const record = raw;
|
|
386
|
+
const fields = record.values ?? record.value ?? record;
|
|
387
|
+
const itemSprints = fieldObjectIds(fields.sprint ?? fields.Sprint ?? fields["迭代"]);
|
|
388
|
+
const isPlanningItem = [
|
|
389
|
+
"Story",
|
|
390
|
+
"EnablerStory",
|
|
391
|
+
"Task"
|
|
392
|
+
].includes(item.itemType);
|
|
393
|
+
if (item.itemKey && isPlanningItem && itemSprints.includes(sprint)) items.set(item.itemKey, {
|
|
394
|
+
...item,
|
|
395
|
+
storyPoints: storyPointValue(fields.StoryPoint),
|
|
396
|
+
tomatoUrl: tomatoItemUrl(config, item.itemKey)
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
const total = Number(payload.count ?? payload.total);
|
|
400
|
+
if (!(Number(payload.nextPageIndex) > page || payload.hasMore === true || payload.hasNext === true || (Number.isFinite(total) ? page * 50 < total : values.length === 50)) || !values.length) return {
|
|
401
|
+
items: [...items.values()].slice(0, limit),
|
|
402
|
+
truncated: items.size > limit
|
|
403
|
+
};
|
|
404
|
+
if (page * 50 >= limit) return {
|
|
405
|
+
items: [...items.values()].slice(0, limit),
|
|
406
|
+
truncated: true
|
|
407
|
+
};
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
function isCrossSite(request) {
|
|
411
|
+
const header = (name) => {
|
|
412
|
+
const value = request.headers?.[name] ?? request.headers?.[name.toLowerCase()];
|
|
413
|
+
return Array.isArray(value) ? value[0] : value;
|
|
414
|
+
};
|
|
415
|
+
if (header("sec-fetch-site") === "cross-site") return true;
|
|
416
|
+
const origin = header("origin");
|
|
417
|
+
const host = header("host");
|
|
418
|
+
if (!origin || !host) return false;
|
|
419
|
+
try {
|
|
420
|
+
return new URL(origin).host !== host;
|
|
421
|
+
} catch {
|
|
422
|
+
return true;
|
|
423
|
+
}
|
|
213
424
|
}
|
|
214
425
|
function sendJson(response, status, body) {
|
|
215
426
|
response.statusCode = status;
|
|
@@ -232,6 +443,70 @@ const name = "tomato-board";
|
|
|
232
443
|
const inject = ["webServer"];
|
|
233
444
|
function apply(ctx, config = {}) {
|
|
234
445
|
const webServer = ctx.get("webServer");
|
|
446
|
+
ctx.effect(() => webServer.register({
|
|
447
|
+
kind: "exact",
|
|
448
|
+
path: "/api/tomato-board/sprints",
|
|
449
|
+
async handler(request, response) {
|
|
450
|
+
if (request.method !== "GET") return sendJson(response, 405, { error: "Method not allowed" });
|
|
451
|
+
try {
|
|
452
|
+
sendJson(response, 200, await loadSprints(config));
|
|
453
|
+
} catch (error) {
|
|
454
|
+
sendJson(response, 502, errorBody(error, "迭代读取失败"));
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
}), "tomato-board: sprints");
|
|
458
|
+
ctx.effect(() => webServer.register({
|
|
459
|
+
kind: "exact",
|
|
460
|
+
path: "/api/tomato-board/story-points",
|
|
461
|
+
async handler(request, response) {
|
|
462
|
+
if (request.method !== "GET") return sendJson(response, 405, { error: "Method not allowed" });
|
|
463
|
+
const query = new URL(request.url ?? "/", "http://localhost").searchParams;
|
|
464
|
+
const sprint = query.get("sprint") ?? "";
|
|
465
|
+
const assignee = query.get("assignee") || "currentUser()";
|
|
466
|
+
if (!/^[A-Za-z0-9_-]{1,128}$/u.test(sprint) || assignee !== "currentUser()" && !/^[A-Za-z0-9_.@-]{1,128}$/u.test(assignee)) return sendJson(response, 400, { error: "无效的迭代或负责人" });
|
|
467
|
+
try {
|
|
468
|
+
sendJson(response, 200, await loadStoryItems(config, sprint, assignee));
|
|
469
|
+
} catch (error) {
|
|
470
|
+
sendJson(response, 502, errorBody(error, "故事点读取失败"));
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
}), "tomato-board: story points");
|
|
474
|
+
ctx.effect(() => webServer.register({
|
|
475
|
+
kind: "prefix",
|
|
476
|
+
path: "/api/tomato-board/story-point",
|
|
477
|
+
async handler(request, response) {
|
|
478
|
+
if (request.method !== "POST") return sendJson(response, 405, { error: "Method not allowed" });
|
|
479
|
+
if (isCrossSite(request)) return sendJson(response, 403, { error: "拒绝跨站修改请求" });
|
|
480
|
+
const itemKey = itemKeyFromRoute(request.url, "/api/tomato-board/story-point");
|
|
481
|
+
const points = storyPointValue(new URL(request.url ?? "/", "http://localhost").searchParams.get("value"));
|
|
482
|
+
if (!itemKey || points === null) return sendJson(response, 400, { error: "请输入非负有限故事点数值" });
|
|
483
|
+
try {
|
|
484
|
+
await runJson(config, [
|
|
485
|
+
"team",
|
|
486
|
+
"item",
|
|
487
|
+
"edit",
|
|
488
|
+
itemKey,
|
|
489
|
+
"--values",
|
|
490
|
+
JSON.stringify({ StoryPoint: points })
|
|
491
|
+
]);
|
|
492
|
+
const raw = await runJson(config, [
|
|
493
|
+
"team",
|
|
494
|
+
"item",
|
|
495
|
+
"view",
|
|
496
|
+
itemKey
|
|
497
|
+
]);
|
|
498
|
+
const actual = storyPointValue((raw.values ?? raw.value ?? raw).StoryPoint);
|
|
499
|
+
if (actual !== points) throw new Error("故事点保存后校验失败,请刷新后重试");
|
|
500
|
+
itemsCache = null;
|
|
501
|
+
sendJson(response, 200, {
|
|
502
|
+
itemKey,
|
|
503
|
+
storyPoints: actual
|
|
504
|
+
});
|
|
505
|
+
} catch (error) {
|
|
506
|
+
sendJson(response, 502, errorBody(error, "故事点保存失败"));
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
}), "tomato-board: edit story point");
|
|
235
510
|
ctx.effect(() => webServer.register({
|
|
236
511
|
kind: "exact",
|
|
237
512
|
path: ROUTE,
|
|
@@ -240,13 +515,30 @@ function apply(ctx, config = {}) {
|
|
|
240
515
|
sendJson(response, 405, { error: "Method not allowed" });
|
|
241
516
|
return;
|
|
242
517
|
}
|
|
518
|
+
const assignee = new URL(request.url ?? ROUTE, "http://localhost").searchParams.get("assignee")?.trim() || "currentUser()";
|
|
519
|
+
if (assignee !== "currentUser()" && !/^[A-Za-z0-9_.@-]{1,128}$/u.test(assignee)) {
|
|
520
|
+
sendJson(response, 400, { error: "无效的负责人用户名" });
|
|
521
|
+
return;
|
|
522
|
+
}
|
|
243
523
|
try {
|
|
244
|
-
sendJson(response, 200,
|
|
524
|
+
sendJson(response, 200, await loadItemsCached(config, assignee));
|
|
245
525
|
} catch (error) {
|
|
246
526
|
sendJson(response, 502, { error: error instanceof Error ? error.message : "番茄事项读取失败" });
|
|
247
527
|
}
|
|
248
528
|
}
|
|
249
529
|
}), "tomato-board: HTTP items route");
|
|
530
|
+
ctx.effect(() => webServer.register({
|
|
531
|
+
kind: "exact",
|
|
532
|
+
path: FILTERS_ROUTE,
|
|
533
|
+
async handler(request, response) {
|
|
534
|
+
if (request.method !== "GET") return sendJson(response, 405, { error: "Method not allowed" });
|
|
535
|
+
try {
|
|
536
|
+
sendJson(response, 200, await loadFilters(config));
|
|
537
|
+
} catch (error) {
|
|
538
|
+
sendJson(response, 502, { error: error instanceof Error ? error.message : "筛选项读取失败" });
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
}), "tomato-board: HTTP filters route");
|
|
250
542
|
ctx.effect(() => webServer.register({
|
|
251
543
|
kind: "prefix",
|
|
252
544
|
path: OPEN_ROUTE,
|
|
@@ -300,6 +592,10 @@ function apply(ctx, config = {}) {
|
|
|
300
592
|
sendJson(response, 405, { error: "Method not allowed" });
|
|
301
593
|
return;
|
|
302
594
|
}
|
|
595
|
+
if (isCrossSite(request)) {
|
|
596
|
+
sendJson(response, 403, { error: "拒绝跨站状态流转请求" });
|
|
597
|
+
return;
|
|
598
|
+
}
|
|
303
599
|
const url = new URL(request.url ?? TRANSITION_ROUTE, "http://localhost");
|
|
304
600
|
const itemKey = itemKeyFromRoute(request.url, TRANSITION_ROUTE);
|
|
305
601
|
const transition = url.searchParams.get("transition")?.trim() ?? "";
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { Dispatch, ReactNode, SetStateAction } from 'react';
|
|
2
|
+
import type { TomatoItem } from './TomatoBoard';
|
|
3
|
+
export declare function StoryPoints({ toolbarTarget, onOpenItem }: {
|
|
4
|
+
toolbarTarget: Dispatch<SetStateAction<ReactNode>>;
|
|
5
|
+
onOpenItem: (item: TomatoItem) => void;
|
|
6
|
+
}): import("react").JSX.Element;
|
|
7
|
+
//# sourceMappingURL=StoryPoints.d.ts.map
|
|
@@ -1,4 +1,17 @@
|
|
|
1
1
|
import type { Context } from '@deepseek-ai/cordis';
|
|
2
|
+
export interface TomatoItem {
|
|
3
|
+
itemKey: string;
|
|
4
|
+
title: string;
|
|
5
|
+
status: string;
|
|
6
|
+
itemType: string;
|
|
7
|
+
workspace: string;
|
|
8
|
+
workspaceKey: string;
|
|
9
|
+
workspaceName: string;
|
|
10
|
+
creator: string;
|
|
11
|
+
assignees: string[];
|
|
12
|
+
priority: string;
|
|
13
|
+
tomatoUrl: string;
|
|
14
|
+
}
|
|
2
15
|
export declare const inject: string[];
|
|
3
16
|
export declare function apply(ctx: Context): void;
|
|
4
17
|
//# sourceMappingURL=TomatoBoard.d.ts.map
|
package/lib/types/index.d.ts
CHANGED
|
@@ -5,6 +5,10 @@ interface Config {
|
|
|
5
5
|
iql?: string;
|
|
6
6
|
tomatoOrigin?: string;
|
|
7
7
|
tomatoTenant?: string;
|
|
8
|
+
cacheTtlMs?: number;
|
|
9
|
+
maxItems?: number;
|
|
10
|
+
excludedStatuses?: string[];
|
|
11
|
+
priorityNames?: Record<string, string>;
|
|
8
12
|
}
|
|
9
13
|
export declare const name = "tomato-board";
|
|
10
14
|
export declare const inject: string[];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stephen1620/dsh-tomato-board",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Gitee Team/Tomato workboard and native conversation launcher for DeepSeek Harness",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -61,7 +61,7 @@
|
|
|
61
61
|
},
|
|
62
62
|
"scripts": {
|
|
63
63
|
"bundle": "tsdown",
|
|
64
|
-
"prepack": "
|
|
64
|
+
"prepack": "node ../../scripts/verify-package.mjs ."
|
|
65
65
|
},
|
|
66
66
|
"peerDependencies": {
|
|
67
67
|
"@deepseek-ai/cordis": "*",
|