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/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, { ok: true, data: cachedData, fromCache: true, cwd });
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, { ok: true, data: cachedGlobal, fromCache: true });
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 [reposRes, prsRes, ownerIssuesRes, assignedIssuesRes, authorIssuesRes] = await Promise.all([
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
  }