dsh-github-flow 0.1.7 → 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 +55 -13
- 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 +95 -4
- package/lib/client.js +851 -40
- package/lib/commands/gh.d.ts +2 -1
- package/lib/commands/gh.js +40 -3
- package/lib/executor.js +12 -2
- package/lib/main.d.ts +11 -0
- package/lib/main.js +41 -3
- package/lib/types.d.ts +45 -0
- 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/package.json +1 -1
package/lib/api/routes.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
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';
|
|
4
6
|
/**
|
|
5
7
|
* 依据当前登录账号判定 Issue 的归属身份维度 (可多选):
|
|
6
8
|
* - reported: 用户提起的 (属于当前账号名下仓库,但作者不是本人)
|
|
@@ -94,11 +96,13 @@ function isSafeExternalUrl(raw) {
|
|
|
94
96
|
return false;
|
|
95
97
|
}
|
|
96
98
|
}
|
|
97
|
-
export function registerApiRoutes(ctxOrWebServer, executor, workspaceRegistry, cacheTtlMs = 15_000) {
|
|
99
|
+
export function registerApiRoutes(ctxOrWebServer, executor, workspaceRegistry, cacheTtlMs = 15_000, updateChecker, upgradeManager) {
|
|
98
100
|
const ctx = ctxOrWebServer;
|
|
99
101
|
const webServer = ctx?.webServer || ctx;
|
|
100
102
|
if (!webServer || typeof webServer.register !== 'function')
|
|
101
103
|
return;
|
|
104
|
+
const checker = updateChecker || new UpdateChecker();
|
|
105
|
+
const upgrader = upgradeManager || new UpgradeManager();
|
|
102
106
|
const cacheMap = new TtlCache(cacheTtlMs);
|
|
103
107
|
const globalCache = new TtlCache(globalCacheTtlMs(cacheTtlMs));
|
|
104
108
|
function sendJson(res, status, data) {
|
|
@@ -162,7 +166,12 @@ export function registerApiRoutes(ctxOrWebServer, executor, workspaceRegistry, c
|
|
|
162
166
|
const now = Date.now();
|
|
163
167
|
const cachedData = cacheMap.get(cwd, now);
|
|
164
168
|
if (cachedData) {
|
|
165
|
-
sendJson(res, 200, {
|
|
169
|
+
sendJson(res, 200, {
|
|
170
|
+
ok: true,
|
|
171
|
+
data: { ...cachedData, updateInfo: checker.getQuickStatus() },
|
|
172
|
+
fromCache: true,
|
|
173
|
+
cwd,
|
|
174
|
+
});
|
|
166
175
|
return;
|
|
167
176
|
}
|
|
168
177
|
const auth = await executor.checkAuth(cwd);
|
|
@@ -202,6 +211,7 @@ export function registerApiRoutes(ctxOrWebServer, executor, workspaceRegistry, c
|
|
|
202
211
|
issueStats,
|
|
203
212
|
runs,
|
|
204
213
|
lastUpdated: new Date().toISOString(),
|
|
214
|
+
updateInfo: checker.getQuickStatus(),
|
|
205
215
|
};
|
|
206
216
|
cacheMap.set(cwd, freshData, now);
|
|
207
217
|
sendJson(res, 200, { ok: true, data: freshData, fromCache: false, cwd });
|
|
@@ -336,7 +346,11 @@ export function registerApiRoutes(ctxOrWebServer, executor, workspaceRegistry, c
|
|
|
336
346
|
const now = Date.now();
|
|
337
347
|
const cachedGlobal = globalCache.get('global', now);
|
|
338
348
|
if (cachedGlobal) {
|
|
339
|
-
sendJson(res, 200, {
|
|
349
|
+
sendJson(res, 200, {
|
|
350
|
+
ok: true,
|
|
351
|
+
data: { ...cachedGlobal, updateInfo: checker.getQuickStatus() },
|
|
352
|
+
fromCache: true,
|
|
353
|
+
});
|
|
340
354
|
return;
|
|
341
355
|
}
|
|
342
356
|
try {
|
|
@@ -355,13 +369,18 @@ export function registerApiRoutes(ctxOrWebServer, executor, workspaceRegistry, c
|
|
|
355
369
|
// - --author=@me: 捕获当前账号亲自发起的任务与向外部提起的议题
|
|
356
370
|
// 3 个轻量进程与 repo/prs 同步 spawn,经 OS 并发调度实测仅耗时 3 秒出头,完全满足 8 秒超时门限。
|
|
357
371
|
const issueFields = 'number,title,repository,author,assignees,labels,url,updatedAt';
|
|
358
|
-
const
|
|
372
|
+
const queryResults = await Promise.allSettled([
|
|
359
373
|
executor.run(['repo', 'list', '--limit', '20', '--json', 'name,nameWithOwner,description,defaultBranchRef,isPrivate,stargazerCount,updatedAt,url'], { timeoutMs: 8_000 }),
|
|
360
374
|
executor.run(['search', 'prs', '--author=@me', '--state=open', '--limit', '10', '--json', 'number,title,repository,url,updatedAt'], { timeoutMs: 8_000 }),
|
|
361
375
|
executor.run(['search', 'issues', '--owner=@me', '--state=open', '--sort=updated', '--limit', '15', '--json', issueFields], { timeoutMs: 8_000 }),
|
|
362
376
|
executor.run(['search', 'issues', '--assignee=@me', '--state=open', '--sort=updated', '--limit', '15', '--json', issueFields], { timeoutMs: 8_000 }),
|
|
363
377
|
executor.run(['search', 'issues', '--author=@me', '--state=open', '--sort=updated', '--limit', '15', '--json', issueFields], { timeoutMs: 8_000 }),
|
|
364
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 };
|
|
365
384
|
if (reposRes.ok && Array.isArray(reposRes.data)) {
|
|
366
385
|
userRepos = reposRes.data.map((r) => ({
|
|
367
386
|
name: r.name || '',
|
|
@@ -441,6 +460,7 @@ export function registerApiRoutes(ctxOrWebServer, executor, workspaceRegistry, c
|
|
|
441
460
|
myIssues,
|
|
442
461
|
issueStats,
|
|
443
462
|
lastUpdated: new Date().toISOString(),
|
|
463
|
+
updateInfo: checker.getQuickStatus(),
|
|
444
464
|
...(workspaceError ? { workspaceError } : {}),
|
|
445
465
|
};
|
|
446
466
|
globalCache.set('global', globalData, now);
|
|
@@ -451,4 +471,75 @@ export function registerApiRoutes(ctxOrWebServer, executor, workspaceRegistry, c
|
|
|
451
471
|
}
|
|
452
472
|
},
|
|
453
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
|
+
});
|
|
454
545
|
}
|