dsh-github-flow 0.1.6 → 0.1.8
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.en.md +200 -67
- package/README.md +199 -65
- package/docs/architecture.md +66 -15
- package/docs/images/1.png +0 -0
- package/docs/images/2.png +0 -0
- package/docs/images/3.png +0 -0
- package/lib/api/routes.d.ts +3 -1
- package/lib/api/routes.js +208 -13
- package/lib/client/panel.js +28 -1
- package/lib/client.js +1002 -43
- package/lib/commands/gh.d.ts +2 -1
- package/lib/commands/gh.js +40 -3
- package/lib/executor.d.ts +6 -5
- package/lib/executor.js +42 -15
- package/lib/main.d.ts +11 -0
- package/lib/main.js +41 -3
- package/lib/tools/api.js +2 -1
- package/lib/tools/issue.js +13 -8
- package/lib/tools/pr.js +14 -9
- package/lib/tools/repo.js +6 -4
- package/lib/tools/run.js +12 -7
- package/lib/types.d.ts +87 -9
- package/lib/updater.d.ts +41 -0
- package/lib/updater.js +160 -0
- package/lib/upgrade-manager.d.ts +54 -0
- package/lib/upgrade-manager.js +299 -0
- package/lib/version.d.ts +13 -0
- package/lib/version.js +13 -0
- package/lib/workspace.d.ts +6 -3
- package/lib/workspace.js +24 -9
- package/package.json +1 -1
package/lib/api/routes.js
CHANGED
|
@@ -1,6 +1,62 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
import { TtlCache } from '../cache.js';
|
|
3
3
|
import { mapLimit, tryReadWorkspaces, findWorkspacePathBySession, findMostRecentWorkspacePath, hasGitDir } from '../workspace.js';
|
|
4
|
+
import { UpdateChecker } from '../updater.js';
|
|
5
|
+
import { UpgradeManager } from '../upgrade-manager.js';
|
|
6
|
+
/**
|
|
7
|
+
* 依据当前登录账号判定 Issue 的归属身份维度 (可多选):
|
|
8
|
+
* - reported: 用户提起的 (属于当前账号名下仓库,但作者不是本人)
|
|
9
|
+
* - assigned: 被分配的 (指派列表中包含当前账号)
|
|
10
|
+
* - created: 我创建的 (作者是当前账号)
|
|
11
|
+
*
|
|
12
|
+
* 为什么这样划分:
|
|
13
|
+
* 在真实工程实践中,Maintainer 不仅要关注自己创建的任务,更要对社区/用户提交的 Bug 反馈负责,
|
|
14
|
+
* 同时还要处理他人指派的待办任务。这三个维度互补且可能重叠。
|
|
15
|
+
*/
|
|
16
|
+
function resolveIssueCategories(issue, currentUser, explicitRepoOwner) {
|
|
17
|
+
const categories = [];
|
|
18
|
+
if (!currentUser)
|
|
19
|
+
return categories;
|
|
20
|
+
const currentLower = currentUser.toLowerCase();
|
|
21
|
+
const authorLogin = issue.author?.login?.toLowerCase();
|
|
22
|
+
const isAuthor = Boolean(authorLogin && authorLogin === currentLower);
|
|
23
|
+
const isAssignee = Array.isArray(issue.assignees) &&
|
|
24
|
+
issue.assignees.some((a) => a.login?.toLowerCase() === currentLower);
|
|
25
|
+
// 仓库 owner 判定:优先从 repository.nameWithOwner 提取,兜底使用 explicitRepoOwner
|
|
26
|
+
let repoOwner = explicitRepoOwner?.toLowerCase();
|
|
27
|
+
if (!repoOwner && issue.repository?.nameWithOwner) {
|
|
28
|
+
repoOwner = issue.repository.nameWithOwner.split('/')[0]?.toLowerCase();
|
|
29
|
+
}
|
|
30
|
+
const isOwnerRepo = Boolean(repoOwner && repoOwner === currentLower);
|
|
31
|
+
if (isAuthor)
|
|
32
|
+
categories.push('created');
|
|
33
|
+
if (isAssignee)
|
|
34
|
+
categories.push('assigned');
|
|
35
|
+
// 如果是当前用户维护的仓库,且作者不是本人,则明确归为「用户提起的」
|
|
36
|
+
if (isOwnerRepo && !isAuthor)
|
|
37
|
+
categories.push('reported');
|
|
38
|
+
return categories;
|
|
39
|
+
}
|
|
40
|
+
function calculateIssueStats(issues) {
|
|
41
|
+
let reported = 0;
|
|
42
|
+
let assigned = 0;
|
|
43
|
+
let created = 0;
|
|
44
|
+
for (const item of issues) {
|
|
45
|
+
const cats = item.categories || [];
|
|
46
|
+
if (cats.includes('reported'))
|
|
47
|
+
reported++;
|
|
48
|
+
if (cats.includes('assigned'))
|
|
49
|
+
assigned++;
|
|
50
|
+
if (cats.includes('created'))
|
|
51
|
+
created++;
|
|
52
|
+
}
|
|
53
|
+
return {
|
|
54
|
+
total: issues.length,
|
|
55
|
+
reported,
|
|
56
|
+
assigned,
|
|
57
|
+
created,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
4
60
|
/** path.basename 在空串/异常输入上会抛错,工作区路径来自磁盘文件,必须防御 */
|
|
5
61
|
function basenameSafe(p) {
|
|
6
62
|
try {
|
|
@@ -40,11 +96,13 @@ function isSafeExternalUrl(raw) {
|
|
|
40
96
|
return false;
|
|
41
97
|
}
|
|
42
98
|
}
|
|
43
|
-
export function registerApiRoutes(ctxOrWebServer, executor, workspaceRegistry, cacheTtlMs = 15_000) {
|
|
99
|
+
export function registerApiRoutes(ctxOrWebServer, executor, workspaceRegistry, cacheTtlMs = 15_000, updateChecker, upgradeManager) {
|
|
44
100
|
const ctx = ctxOrWebServer;
|
|
45
101
|
const webServer = ctx?.webServer || ctx;
|
|
46
102
|
if (!webServer || typeof webServer.register !== 'function')
|
|
47
103
|
return;
|
|
104
|
+
const checker = updateChecker || new UpdateChecker();
|
|
105
|
+
const upgrader = upgradeManager || new UpgradeManager();
|
|
48
106
|
const cacheMap = new TtlCache(cacheTtlMs);
|
|
49
107
|
const globalCache = new TtlCache(globalCacheTtlMs(cacheTtlMs));
|
|
50
108
|
function sendJson(res, status, data) {
|
|
@@ -108,7 +166,12 @@ export function registerApiRoutes(ctxOrWebServer, executor, workspaceRegistry, c
|
|
|
108
166
|
const now = Date.now();
|
|
109
167
|
const cachedData = cacheMap.get(cwd, now);
|
|
110
168
|
if (cachedData) {
|
|
111
|
-
sendJson(res, 200, {
|
|
169
|
+
sendJson(res, 200, {
|
|
170
|
+
ok: true,
|
|
171
|
+
data: { ...cachedData, updateInfo: checker.getQuickStatus() },
|
|
172
|
+
fromCache: true,
|
|
173
|
+
cwd,
|
|
174
|
+
});
|
|
112
175
|
return;
|
|
113
176
|
}
|
|
114
177
|
const auth = await executor.checkAuth(cwd);
|
|
@@ -125,18 +188,30 @@ export function registerApiRoutes(ctxOrWebServer, executor, workspaceRegistry, c
|
|
|
125
188
|
]);
|
|
126
189
|
if (prsRes.ok && Array.isArray(prsRes.data))
|
|
127
190
|
pullRequests = prsRes.data;
|
|
128
|
-
if (issuesRes.ok && Array.isArray(issuesRes.data))
|
|
129
|
-
|
|
191
|
+
if (issuesRes.ok && Array.isArray(issuesRes.data)) {
|
|
192
|
+
const currentUser = auth.user;
|
|
193
|
+
const repoOwner = repo?.owner || repo?.nameWithOwner?.split('/')[0];
|
|
194
|
+
issues = issuesRes.data.map((item) => {
|
|
195
|
+
const categories = resolveIssueCategories(item, currentUser, repoOwner);
|
|
196
|
+
return {
|
|
197
|
+
...item,
|
|
198
|
+
categories,
|
|
199
|
+
};
|
|
200
|
+
});
|
|
201
|
+
}
|
|
130
202
|
if (runsRes.ok && Array.isArray(runsRes.data))
|
|
131
203
|
runs = runsRes.data;
|
|
132
204
|
}
|
|
205
|
+
const issueStats = calculateIssueStats(issues);
|
|
133
206
|
const freshData = {
|
|
134
207
|
auth,
|
|
135
208
|
repo: repo || undefined,
|
|
136
209
|
pullRequests,
|
|
137
210
|
issues,
|
|
211
|
+
issueStats,
|
|
138
212
|
runs,
|
|
139
213
|
lastUpdated: new Date().toISOString(),
|
|
214
|
+
updateInfo: checker.getQuickStatus(),
|
|
140
215
|
};
|
|
141
216
|
cacheMap.set(cwd, freshData, now);
|
|
142
217
|
sendJson(res, 200, { ok: true, data: freshData, fromCache: false, cwd });
|
|
@@ -271,7 +346,11 @@ export function registerApiRoutes(ctxOrWebServer, executor, workspaceRegistry, c
|
|
|
271
346
|
const now = Date.now();
|
|
272
347
|
const cachedGlobal = globalCache.get('global', now);
|
|
273
348
|
if (cachedGlobal) {
|
|
274
|
-
sendJson(res, 200, {
|
|
349
|
+
sendJson(res, 200, {
|
|
350
|
+
ok: true,
|
|
351
|
+
data: { ...cachedGlobal, updateInfo: checker.getQuickStatus() },
|
|
352
|
+
fromCache: true,
|
|
353
|
+
});
|
|
275
354
|
return;
|
|
276
355
|
}
|
|
277
356
|
try {
|
|
@@ -279,17 +358,29 @@ export function registerApiRoutes(ctxOrWebServer, executor, workspaceRegistry, c
|
|
|
279
358
|
let userRepos = [];
|
|
280
359
|
let myPrs = [];
|
|
281
360
|
let myIssues = [];
|
|
361
|
+
let issueStats = { total: 0, reported: 0, assigned: 0, created: 0 };
|
|
282
362
|
let workspaceMatrix = [];
|
|
283
363
|
let workspaceError;
|
|
284
364
|
if (auth.loggedIn) {
|
|
285
|
-
// 并发执行:1. 账号下仓库列表 2. 个人 PRs 3.
|
|
286
|
-
//
|
|
287
|
-
//
|
|
288
|
-
|
|
365
|
+
// 并发执行:1. 账号下仓库列表 2. 个人 PRs 3. 三维 Issue 检索(名下仓库用户提单 + 指派待办 + 我创建的)
|
|
366
|
+
// 为什么三路并发搜索:
|
|
367
|
+
// - --owner=@me: 捕获用户/访客在当前用户所有仓库下提的 Issue (如反馈 Bug、Feature Request),解决外部提单看不见的问题
|
|
368
|
+
// - --assignee=@me: 捕获团队成员或自己指派给当前账号的待办任务 (含跨组织/第三方项目)
|
|
369
|
+
// - --author=@me: 捕获当前账号亲自发起的任务与向外部提起的议题
|
|
370
|
+
// 3 个轻量进程与 repo/prs 同步 spawn,经 OS 并发调度实测仅耗时 3 秒出头,完全满足 8 秒超时门限。
|
|
371
|
+
const issueFields = 'number,title,repository,author,assignees,labels,url,updatedAt';
|
|
372
|
+
const queryResults = await Promise.allSettled([
|
|
289
373
|
executor.run(['repo', 'list', '--limit', '20', '--json', 'name,nameWithOwner,description,defaultBranchRef,isPrivate,stargazerCount,updatedAt,url'], { timeoutMs: 8_000 }),
|
|
290
374
|
executor.run(['search', 'prs', '--author=@me', '--state=open', '--limit', '10', '--json', 'number,title,repository,url,updatedAt'], { timeoutMs: 8_000 }),
|
|
291
|
-
executor.run(['search', 'issues', '--
|
|
375
|
+
executor.run(['search', 'issues', '--owner=@me', '--state=open', '--sort=updated', '--limit', '15', '--json', issueFields], { timeoutMs: 8_000 }),
|
|
376
|
+
executor.run(['search', 'issues', '--assignee=@me', '--state=open', '--sort=updated', '--limit', '15', '--json', issueFields], { timeoutMs: 8_000 }),
|
|
377
|
+
executor.run(['search', 'issues', '--author=@me', '--state=open', '--sort=updated', '--limit', '15', '--json', issueFields], { timeoutMs: 8_000 }),
|
|
292
378
|
]);
|
|
379
|
+
const reposRes = queryResults[0].status === 'fulfilled' ? queryResults[0].value : { ok: false };
|
|
380
|
+
const prsRes = queryResults[1].status === 'fulfilled' ? queryResults[1].value : { ok: false };
|
|
381
|
+
const ownerIssuesRes = queryResults[2].status === 'fulfilled' ? queryResults[2].value : { ok: false };
|
|
382
|
+
const assignedIssuesRes = queryResults[3].status === 'fulfilled' ? queryResults[3].value : { ok: false };
|
|
383
|
+
const authorIssuesRes = queryResults[4].status === 'fulfilled' ? queryResults[4].value : { ok: false };
|
|
293
384
|
if (reposRes.ok && Array.isArray(reposRes.data)) {
|
|
294
385
|
userRepos = reposRes.data.map((r) => ({
|
|
295
386
|
name: r.name || '',
|
|
@@ -305,9 +396,40 @@ export function registerApiRoutes(ctxOrWebServer, executor, workspaceRegistry, c
|
|
|
305
396
|
if (prsRes.ok && Array.isArray(prsRes.data)) {
|
|
306
397
|
myPrs = prsRes.data;
|
|
307
398
|
}
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
399
|
+
// 多源 Issues 聚合、按 URL 唯一去重并排序
|
|
400
|
+
const issueMap = new Map();
|
|
401
|
+
const collectIssues = (res) => {
|
|
402
|
+
if (res.ok && Array.isArray(res.data)) {
|
|
403
|
+
for (const item of res.data) {
|
|
404
|
+
if (item.url && !issueMap.has(item.url)) {
|
|
405
|
+
issueMap.set(item.url, item);
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
};
|
|
410
|
+
collectIssues(ownerIssuesRes);
|
|
411
|
+
collectIssues(assignedIssuesRes);
|
|
412
|
+
collectIssues(authorIssuesRes);
|
|
413
|
+
const rawList = Array.from(issueMap.values());
|
|
414
|
+
// 统一按最后更新时间降序排列
|
|
415
|
+
rawList.sort((a, b) => new Date(b.updatedAt || 0).getTime() - new Date(a.updatedAt || 0).getTime());
|
|
416
|
+
const currentUser = auth.user;
|
|
417
|
+
myIssues = rawList.map((raw) => {
|
|
418
|
+
const categories = resolveIssueCategories(raw, currentUser);
|
|
419
|
+
return {
|
|
420
|
+
number: raw.number,
|
|
421
|
+
title: raw.title || '',
|
|
422
|
+
repository: raw.repository || { nameWithOwner: '' },
|
|
423
|
+
author: raw.author ? { login: raw.author.login } : undefined,
|
|
424
|
+
assignees: Array.isArray(raw.assignees) ? raw.assignees.map((a) => ({ login: a.login })) : [],
|
|
425
|
+
labels: Array.isArray(raw.labels) ? raw.labels.map((l) => ({ name: l.name, color: l.color })) : [],
|
|
426
|
+
url: raw.url || '',
|
|
427
|
+
updatedAt: raw.updatedAt || '',
|
|
428
|
+
state: raw.state || 'OPEN',
|
|
429
|
+
categories,
|
|
430
|
+
};
|
|
431
|
+
});
|
|
432
|
+
issueStats = calculateIssueStats(myIssues);
|
|
311
433
|
}
|
|
312
434
|
// 解析本地所有工作区与 GitHub 关联矩阵
|
|
313
435
|
const wsRead = tryReadWorkspaces();
|
|
@@ -336,7 +458,9 @@ export function registerApiRoutes(ctxOrWebServer, executor, workspaceRegistry, c
|
|
|
336
458
|
workspaceMatrix,
|
|
337
459
|
myPrs,
|
|
338
460
|
myIssues,
|
|
461
|
+
issueStats,
|
|
339
462
|
lastUpdated: new Date().toISOString(),
|
|
463
|
+
updateInfo: checker.getQuickStatus(),
|
|
340
464
|
...(workspaceError ? { workspaceError } : {}),
|
|
341
465
|
};
|
|
342
466
|
globalCache.set('global', globalData, now);
|
|
@@ -347,4 +471,75 @@ export function registerApiRoutes(ctxOrWebServer, executor, workspaceRegistry, c
|
|
|
347
471
|
}
|
|
348
472
|
},
|
|
349
473
|
});
|
|
474
|
+
// 5. 闲时静默检查更新端点(前端在 gh 业务数据完全拉取就绪后,由 idle 延时静默触发)
|
|
475
|
+
bindRoute({
|
|
476
|
+
kind: 'exact',
|
|
477
|
+
path: '/api/github/check-update',
|
|
478
|
+
handler: async (req, res) => {
|
|
479
|
+
if (req.method !== 'GET') {
|
|
480
|
+
sendJson(res, 405, { ok: false, message: '仅支持 GET 请求' });
|
|
481
|
+
return;
|
|
482
|
+
}
|
|
483
|
+
try {
|
|
484
|
+
const url = new URL(req.url || '', 'http://127.0.0.1');
|
|
485
|
+
const force = url.searchParams.get('force') === 'true';
|
|
486
|
+
const info = await checker.check(force);
|
|
487
|
+
sendJson(res, 200, { ok: true, data: info });
|
|
488
|
+
}
|
|
489
|
+
catch (err) {
|
|
490
|
+
sendJson(res, 500, { ok: false, error: err.message || '检查更新失败' });
|
|
491
|
+
}
|
|
492
|
+
},
|
|
493
|
+
});
|
|
494
|
+
// 6. 一键自动升级触发端点(用户点击操作横幅按钮后发起)
|
|
495
|
+
bindRoute({
|
|
496
|
+
kind: 'exact',
|
|
497
|
+
path: '/api/github/upgrade',
|
|
498
|
+
handler: async (req, res) => {
|
|
499
|
+
if (req.method !== 'POST') {
|
|
500
|
+
sendJson(res, 405, { ok: false, message: '仅支持 POST 请求' });
|
|
501
|
+
return;
|
|
502
|
+
}
|
|
503
|
+
try {
|
|
504
|
+
const info = await checker.check(false);
|
|
505
|
+
const result = await upgrader.executeUpgrade(info.latestVersion);
|
|
506
|
+
sendJson(res, result.ok ? 200 : 500, result);
|
|
507
|
+
}
|
|
508
|
+
catch (err) {
|
|
509
|
+
sendJson(res, 500, { ok: false, message: err.message || '执行自动升级异常' });
|
|
510
|
+
}
|
|
511
|
+
},
|
|
512
|
+
});
|
|
513
|
+
// 7. 升级状态与实时日志查询端点
|
|
514
|
+
bindRoute({
|
|
515
|
+
kind: 'exact',
|
|
516
|
+
path: '/api/github/upgrade-status',
|
|
517
|
+
handler: async (req, res) => {
|
|
518
|
+
if (req.method !== 'GET') {
|
|
519
|
+
sendJson(res, 405, { ok: false, message: '仅支持 GET 请求' });
|
|
520
|
+
return;
|
|
521
|
+
}
|
|
522
|
+
sendJson(res, 200, { ok: true, data: upgrader.getStatus() });
|
|
523
|
+
},
|
|
524
|
+
});
|
|
525
|
+
// 8. 快速认证状态轻量探针 (耗时 < 500ms,专供登录自愈检测与快速恢复)
|
|
526
|
+
bindRoute({
|
|
527
|
+
kind: 'exact',
|
|
528
|
+
path: '/api/github/auth',
|
|
529
|
+
handler: async (req, res) => {
|
|
530
|
+
if (req.method !== 'GET') {
|
|
531
|
+
sendJson(res, 405, { ok: false, message: '仅支持 GET 请求' });
|
|
532
|
+
return;
|
|
533
|
+
}
|
|
534
|
+
try {
|
|
535
|
+
const url = new URL(req.url || '', 'http://127.0.0.1');
|
|
536
|
+
const force = url.searchParams.get('force') === 'true';
|
|
537
|
+
const auth = await executor.checkAuth(undefined, force);
|
|
538
|
+
sendJson(res, 200, { ok: true, auth });
|
|
539
|
+
}
|
|
540
|
+
catch (err) {
|
|
541
|
+
sendJson(res, 500, { ok: false, error: err.message || '认证检测失败' });
|
|
542
|
+
}
|
|
543
|
+
},
|
|
544
|
+
});
|
|
350
545
|
}
|
package/lib/client/panel.js
CHANGED
|
@@ -62,6 +62,33 @@ export function createGithubPanelComponent() {
|
|
|
62
62
|
: h('div', { className: 'dsh-github-list' }, prs.map((pr) => h('div', { key: pr.number, className: 'dsh-github-item' }, h('div', { className: 'dsh-github-item-main' }, h('a', { className: 'dsh-github-item-title', href: pr.url, target: '_blank' }, `#${pr.number} ${pr.title}`), h('div', { className: 'dsh-github-item-meta' }, h('span', null, `由 ${pr.author?.login || '未知'} 提交`), h('span', null, `${pr.headRefName} → ${pr.baseRefName}`), pr.mergeable === 'CONFLICTING'
|
|
63
63
|
? h('span', { className: 'dsh-github-badge dsh-badge-red' }, '冲突 Conflicting')
|
|
64
64
|
: h('span', { className: 'dsh-github-badge dsh-badge-green' }, '可合并 Mergeable'))), h('div', { className: 'dsh-github-item-actions' }, h('a', { className: 'dsh-github-btn-sm', href: pr.url, target: '_blank', style: { textDecoration: 'none' } }, '在 GitHub 打开'))))));
|
|
65
|
+
// Issues 区域
|
|
66
|
+
const issuesSectionEl = h('div', { className: 'dsh-github-section' }, h('div', { className: 'dsh-github-section-header' }, h('h3', null, `📋 仓库 Issues (${issues.length})`), repo?.url
|
|
67
|
+
? h('a', {
|
|
68
|
+
href: `${repo.url}/issues`,
|
|
69
|
+
target: '_blank',
|
|
70
|
+
style: { fontSize: '11px', color: 'var(--dsw-alias-link, #0969da)', textDecoration: 'none' },
|
|
71
|
+
}, '全部 ↗')
|
|
72
|
+
: null), issues.length === 0
|
|
73
|
+
? h('div', { className: 'dsh-github-empty' }, '当前无开放中的 Issue')
|
|
74
|
+
: h('div', { className: 'dsh-github-list' }, issues.map((issue) => {
|
|
75
|
+
const cats = issue.categories || [];
|
|
76
|
+
const badges = [];
|
|
77
|
+
if (cats.includes('reported')) {
|
|
78
|
+
badges.push(h('span', { key: 'rep', className: 'dsh-github-badge dsh-badge-purple' }, '用户反馈'));
|
|
79
|
+
}
|
|
80
|
+
if (cats.includes('assigned')) {
|
|
81
|
+
badges.push(h('span', { key: 'asn', className: 'dsh-github-badge dsh-badge-blue' }, '已分配'));
|
|
82
|
+
}
|
|
83
|
+
if (cats.includes('created')) {
|
|
84
|
+
badges.push(h('span', { key: 'crt', className: 'dsh-github-badge dsh-badge-green' }, '我发起的'));
|
|
85
|
+
}
|
|
86
|
+
return h('div', { key: issue.number, className: 'dsh-github-item' }, h('div', { className: 'dsh-github-item-main' }, h('div', { style: { display: 'flex', alignItems: 'center', gap: '6px', flexWrap: 'wrap' } }, h('a', { className: 'dsh-github-item-title', href: issue.url, target: '_blank' }, `#${issue.number} ${issue.title}`), ...badges), h('div', { className: 'dsh-github-item-meta' }, h('span', null, `由 ${issue.author?.login || '未知'} 提出`), issue.assignees && issue.assignees.length > 0
|
|
87
|
+
? h('span', null, `指派: ${issue.assignees.map((a) => a.login).join(', ')}`)
|
|
88
|
+
: null, issue.labels && issue.labels.length > 0
|
|
89
|
+
? issue.labels.map((lbl) => h('span', { key: lbl.name, className: 'dsh-github-badge dsh-badge-yellow' }, lbl.name))
|
|
90
|
+
: null)), h('div', { className: 'dsh-github-item-actions' }, h('a', { className: 'dsh-github-btn-sm', href: issue.url, target: '_blank', style: { textDecoration: 'none' } }, '在 GitHub 打开')));
|
|
91
|
+
})));
|
|
65
92
|
// GitHub Actions 区域
|
|
66
93
|
const runsSectionEl = h('div', { className: 'dsh-github-section' }, h('div', { className: 'dsh-github-section-header' }, h('h3', null, `⚡ GitHub Actions 最近流水线 (${runs.length})`)), runs.length === 0
|
|
67
94
|
? h('div', { className: 'dsh-github-empty' }, '暂无工作流运行记录')
|
|
@@ -70,6 +97,6 @@ export function createGithubPanelComponent() {
|
|
|
70
97
|
: r.conclusion === 'failure'
|
|
71
98
|
? h('span', { className: 'dsh-github-badge dsh-badge-red' }, '失败 Failure')
|
|
72
99
|
: h('span', { className: 'dsh-github-badge dsh-badge-yellow' }, r.status || '进行中'))), h('div', { className: 'dsh-github-item-actions' }, h('a', { className: 'dsh-github-btn-sm', href: r.url, target: '_blank', style: { textDecoration: 'none' } }, '查看流水线'))))));
|
|
73
|
-
return h('div', { className: 'dsh-github-panel' }, headerEl, cardsEl, prsSectionEl, runsSectionEl);
|
|
100
|
+
return h('div', { className: 'dsh-github-panel' }, headerEl, cardsEl, prsSectionEl, issuesSectionEl, runsSectionEl);
|
|
74
101
|
};
|
|
75
102
|
}
|