dsh-github-flow 0.2.0 → 0.3.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.
Files changed (44) hide show
  1. package/README.en.md +68 -22
  2. package/README.md +69 -23
  3. package/docs/UPGRADE-NOTES.md +93 -0
  4. package/docs/architecture.md +76 -31
  5. package/docs/images/1.png +0 -0
  6. package/docs/images/2.png +0 -0
  7. package/docs/images/3.png +0 -0
  8. package/docs/images/4.png +0 -0
  9. package/docs/npm-publish-guide.md +27 -5
  10. package/lib/api/routes-actions.d.ts +18 -4
  11. package/lib/api/routes-actions.js +143 -19
  12. package/lib/api/routes-lifecycle.d.ts +1 -1
  13. package/lib/api/routes-lifecycle.js +53 -2
  14. package/lib/api/routes-overview.js +104 -1
  15. package/lib/client.js +991 -55
  16. package/lib/commands/gh.js +11 -2
  17. package/lib/executor.d.ts +118 -1
  18. package/lib/executor.js +479 -12
  19. package/lib/main.js +10 -0
  20. package/lib/renderers/markdown.d.ts +12 -1
  21. package/lib/renderers/markdown.js +150 -16
  22. package/lib/tools/help.js +144 -11
  23. package/lib/tools/image-utils.d.ts +79 -0
  24. package/lib/tools/image-utils.js +247 -0
  25. package/lib/tools/issue.d.ts +18 -4
  26. package/lib/tools/issue.js +161 -6
  27. package/lib/tools/pr.d.ts +12 -4
  28. package/lib/tools/pr.js +128 -7
  29. package/lib/tools/repo.d.ts +20 -4
  30. package/lib/tools/repo.js +196 -4
  31. package/lib/tools/run.d.ts +8 -0
  32. package/lib/tools/run.js +104 -3
  33. package/lib/tools/secret.d.ts +5 -0
  34. package/lib/tools/secret.js +108 -23
  35. package/lib/types.d.ts +85 -1
  36. package/lib/update-settings.d.ts +18 -0
  37. package/lib/update-settings.js +60 -0
  38. package/lib/updater.d.ts +32 -1
  39. package/lib/updater.js +117 -9
  40. package/lib/upgrade-manager.d.ts +70 -1
  41. package/lib/upgrade-manager.js +346 -46
  42. package/lib/version.d.ts +1 -1
  43. package/lib/version.js +1 -1
  44. package/package.json +1 -1
@@ -43,6 +43,9 @@ export function registerOverviewRoutes(deps, executor, cacheMap, globalCache, ch
43
43
  let pullRequests = [];
44
44
  let issues = [];
45
45
  let runs = [];
46
+ // 面板增强信息载体(git 状态/Release 锚定/分支 PR——全部可选字段,
47
+ // 进缓存与快照时旧消费方零破坏,渲染层 ?. 兜底)
48
+ const freshExtras = {};
46
49
  if (repo && auth.loggedIn) {
47
50
  // 并发拉取当前文件夹仓库的 PRs、Issues 和 Runs
48
51
  const [prsRes, issuesRes, runsRes] = await Promise.all([
@@ -65,6 +68,34 @@ export function registerOverviewRoutes(deps, executor, cacheMap, globalCache, ch
65
68
  }
66
69
  if (runsRes.ok && Array.isArray(runsRes.data))
67
70
  runs = runsRes.data;
71
+ // 面板信息增强(三路并行,全部失败静默——可选装饰信息不阻塞主流程):
72
+ // 1. git 本地状态:纯本地毫秒级(remote-tracking 快照),零网络
73
+ // 2. Release 锚定:gh release view,无 Release 仓库是常态非错误
74
+ // 3. 当前分支↔PR 关联:pr status -c,无 PR 分支同样静默
75
+ const [gitStatus, releaseInfo, branchPr] = await Promise.all([
76
+ executor.getLocalGitStatus(cwd).catch(() => null),
77
+ executor.run(['release', 'view', '--json', 'tagName,isPrerelease,isDraft,publishedAt,url', '-R', repo.nameWithOwner], { cwd, timeoutMs: 8_000 }).then((r) => (r.ok && r.data && !r.data.isDraft ? r.data : null)).catch(() => null),
78
+ executor.run(['pr', 'status', '-c', '--json', 'number,title,state,url,isDraft', '-R', repo.nameWithOwner], { cwd, timeoutMs: 8_000 }).then((r) => {
79
+ // 实测键集:{ createdBy: [...], needsReview: [...] }——「当前分支
80
+ // 关联 PR」按优先级取 createdBy(自己发起)→ needsReview(待审)。
81
+ // 不传 rawText:executor 对含 --json 的命令自动解析出 data——
82
+ // rawText 模式 data 恒 undefined 会让本功能静默失效(审查抓到的真实 Bug)
83
+ if (!r.ok || !r.data)
84
+ return null;
85
+ const createdBy = Array.isArray(r.data?.createdBy) ? r.data.createdBy : [];
86
+ const needsReview = Array.isArray(r.data?.needsReview) ? r.data.needsReview : [];
87
+ return createdBy[0] || needsReview[0] || null;
88
+ }).catch(() => null),
89
+ ]);
90
+ freshExtras.gitStatus = gitStatus;
91
+ freshExtras.releaseInfo = releaseInfo;
92
+ freshExtras.branchPr = branchPr;
93
+ // 4. git 提交记录三段(领先/落后/最近):依赖 gitStatus.hasUpstream 守卫,
94
+ // 在并行波次之后串行补(本地毫秒级,无感知延迟)——放进第一波会因
95
+ // Promise.all 参数在调用时求值而引用未解构的 gitStatus(恒 undefined)
96
+ freshExtras.commitLog = await executor
97
+ .getGitCommitLog(cwd, gitStatus ? gitStatus.hasUpstream : false)
98
+ .catch(() => null);
68
99
  }
69
100
  const issueStats = calculateIssueStats(issues);
70
101
  const freshData = {
@@ -76,6 +107,7 @@ export function registerOverviewRoutes(deps, executor, cacheMap, globalCache, ch
76
107
  runs,
77
108
  lastUpdated: new Date().toISOString(),
78
109
  updateInfo: checker.getQuickStatus(),
110
+ ...freshExtras,
79
111
  };
80
112
  cacheMap.set(cwd, freshData, now);
81
113
  sendJson(res, 200, { ok: true, data: freshData, fromCache: false, cwd });
@@ -124,7 +156,7 @@ export function registerOverviewRoutes(deps, executor, cacheMap, globalCache, ch
124
156
  const issueFields = 'number,title,repository,author,assignees,labels,url,updatedAt';
125
157
  const queryResults = await Promise.allSettled([
126
158
  executor.run(['repo', 'list', '--limit', '20', '--json', 'name,nameWithOwner,description,defaultBranchRef,isPrivate,stargazerCount,updatedAt,url'], { timeoutMs: 8_000 }),
127
- executor.run(['search', 'prs', '--author=@me', '--state=open', '--limit', '10', '--json', 'number,title,repository,url,updatedAt'], { timeoutMs: 8_000 }),
159
+ executor.run(['search', 'prs', '--author=@me', '--state=open', '--limit', '10', '--json', 'number,title,repository,url,updatedAt,isDraft'], { timeoutMs: 8_000 }),
128
160
  executor.run(['search', 'issues', '--owner=@me', '--state=open', '--sort=updated', '--limit', '15', '--json', issueFields], { timeoutMs: 8_000 }),
129
161
  executor.run(['search', 'issues', '--assignee=@me', '--state=open', '--sort=updated', '--limit', '15', '--json', issueFields], { timeoutMs: 8_000 }),
130
162
  executor.run(['search', 'issues', '--author=@me', '--state=open', '--sort=updated', '--limit', '15', '--json', issueFields], { timeoutMs: 8_000 }),
@@ -195,6 +227,9 @@ export function registerOverviewRoutes(deps, executor, cacheMap, globalCache, ch
195
227
  // 每次网络往返都要秒级,5 个工作区就足以把该端点拖到 20 秒。
196
228
  workspaceMatrix = await mapLimit(wsEntries, WORKSPACE_PROBE_CONCURRENCY, async (ws) => {
197
229
  const hasGit = hasGitDir(ws.path);
230
+ // git 状态与身份识别同为纯本地探测(remote-tracking 快照零网络),
231
+ // 挂进同一 mapLimit 有界并发——不新增并发维度,不碰性能红线
232
+ const gitStatus = hasGit ? await executor.getLocalGitStatus(ws.path).catch(() => null) : null;
198
233
  return {
199
234
  id: ws.id,
200
235
  title: ws.title || basenameSafe(ws.path),
@@ -202,6 +237,7 @@ export function registerOverviewRoutes(deps, executor, cacheMap, globalCache, ch
202
237
  hasGit,
203
238
  repo: hasGit ? executor.getRepoIdentityFromLocal(ws.path) || undefined : undefined,
204
239
  sessionCount: ws.sessionIds.length,
240
+ ...(gitStatus ? { gitStatus } : {}),
205
241
  };
206
242
  });
207
243
  }
@@ -224,4 +260,71 @@ export function registerOverviewRoutes(deps, executor, cacheMap, globalCache, ch
224
260
  }
225
261
  },
226
262
  });
263
+ // 3. 单条提交详情(Git 记录页点击行的惰性查询——弹窗打开时才发起,
264
+ // 不进 overview 缓存:低频单查 + git show 本地毫秒级,无缓存必要)
265
+ bindRoute({
266
+ kind: 'exact',
267
+ path: '/api/github/commit-detail',
268
+ handler: async (req, res) => {
269
+ if (req.method !== 'GET') {
270
+ sendJson(res, 405, { ok: false, message: '仅支持 GET 请求' });
271
+ return;
272
+ }
273
+ try {
274
+ const url = new URL(req.url || '', 'http://127.0.0.1');
275
+ let cwd = url.searchParams.get('cwd') || undefined;
276
+ const sessionId = url.searchParams.get('sessionId');
277
+ if (!cwd && sessionId) {
278
+ cwd = findWorkspacePathBySession(sessionId) || undefined;
279
+ }
280
+ if (!cwd) {
281
+ cwd = findMostRecentWorkspacePath() || undefined;
282
+ }
283
+ const sha = url.searchParams.get('sha') || '';
284
+ // sha 形态校验双保险(executor 内还有一道):路由层先拦明显恶意值
285
+ if (!/^[0-9a-f]{4,40}$/i.test(sha)) {
286
+ sendJson(res, 400, { ok: false, error: '非法 sha(仅接受 4-40 位十六进制)' });
287
+ return;
288
+ }
289
+ const detail = await executor.getGitCommitDetail(cwd, sha);
290
+ if (detail === null) {
291
+ sendJson(res, 404, { ok: false, error: '未找到该提交(可能不在当前仓库)' });
292
+ return;
293
+ }
294
+ sendJson(res, 200, { ok: true, detail });
295
+ }
296
+ catch (err) {
297
+ sendJson(res, 500, { ok: false, error: err.message || '查询提交详情失败' });
298
+ }
299
+ },
300
+ });
301
+ // 4. 分页获取最近提交记录(Git 记录页下滑触底自动加载或点击加载更多)
302
+ bindRoute({
303
+ kind: 'exact',
304
+ path: '/api/github/git-log',
305
+ handler: async (req, res) => {
306
+ if (req.method !== 'GET') {
307
+ sendJson(res, 405, { ok: false, message: '仅支持 GET 请求' });
308
+ return;
309
+ }
310
+ try {
311
+ const url = new URL(req.url || '', 'http://127.0.0.1');
312
+ let cwd = url.searchParams.get('cwd') || undefined;
313
+ const sessionId = url.searchParams.get('sessionId');
314
+ if (!cwd && sessionId) {
315
+ cwd = findWorkspacePathBySession(sessionId) || undefined;
316
+ }
317
+ if (!cwd) {
318
+ cwd = findMostRecentWorkspacePath() || undefined;
319
+ }
320
+ const limit = Math.min(Math.max(1, Number(url.searchParams.get('limit')) || 15), 50);
321
+ const skip = Math.max(0, Number(url.searchParams.get('skip')) || 0);
322
+ const result = await executor.getGitRecentLog(cwd, limit, skip);
323
+ sendJson(res, 200, { ok: true, data: result });
324
+ }
325
+ catch (err) {
326
+ sendJson(res, 500, { ok: false, error: err.message || '获取提交记录失败' });
327
+ }
328
+ },
329
+ });
227
330
  }