dsh-github-flow 0.2.1 → 0.4.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.en.md +234 -130
- package/README.md +217 -112
- package/docs/UPGRADE-NOTES.md +15 -9
- package/docs/architecture.md +120 -108
- package/docs/images/2.png +0 -0
- package/docs/npm-publish-guide.md +12 -1
- package/lib/api/routes-actions.js +117 -0
- package/lib/api/routes-overview.js +198 -3
- package/lib/cache.d.ts +4 -0
- package/lib/cache.js +14 -0
- package/lib/client.d.ts +13 -1
- package/lib/client.js +3162 -2162
- package/lib/executor/git-parser.d.ts +25 -0
- package/lib/executor/git-parser.js +143 -0
- package/lib/executor/platform-guide.d.ts +14 -0
- package/lib/executor/platform-guide.js +40 -0
- package/lib/executor/process-utils.d.ts +51 -0
- package/lib/executor/process-utils.js +162 -0
- package/lib/executor/version-utils.d.ts +12 -0
- package/lib/executor/version-utils.js +27 -0
- package/lib/executor.d.ts +131 -17
- package/lib/executor.js +411 -223
- package/lib/git-watcher.d.ts +36 -0
- package/lib/git-watcher.js +91 -0
- package/lib/main.js +11 -0
- package/lib/renderers/cache.d.ts +8 -0
- package/lib/renderers/cache.js +66 -0
- package/lib/renderers/common.d.ts +24 -0
- package/lib/renderers/common.js +88 -0
- package/lib/renderers/gist.d.ts +15 -0
- package/lib/renderers/gist.js +99 -0
- package/lib/renderers/issue.d.ts +4 -0
- package/lib/renderers/issue.js +70 -0
- package/lib/renderers/label.d.ts +4 -0
- package/lib/renderers/label.js +48 -0
- package/lib/renderers/markdown.d.ts +15 -26
- package/lib/renderers/markdown.js +15 -517
- package/lib/renderers/pr.d.ts +4 -0
- package/lib/renderers/pr.js +107 -0
- package/lib/renderers/release.d.ts +4 -0
- package/lib/renderers/release.js +74 -0
- package/lib/renderers/repo.d.ts +4 -0
- package/lib/renderers/repo.js +149 -0
- package/lib/renderers/ruleset.d.ts +4 -0
- package/lib/renderers/ruleset.js +74 -0
- package/lib/renderers/run.d.ts +4 -0
- package/lib/renderers/run.js +79 -0
- package/lib/renderers/skill.d.ts +4 -0
- package/lib/renderers/skill.js +61 -0
- package/lib/renderers/status.d.ts +23 -0
- package/lib/renderers/status.js +124 -0
- package/lib/tools/cache.d.ts +64 -0
- package/lib/tools/cache.js +186 -0
- package/lib/tools/gist.d.ts +155 -0
- package/lib/tools/gist.js +190 -0
- package/lib/tools/help.js +233 -11
- package/lib/tools/image-utils.d.ts +8 -0
- package/lib/tools/image-utils.js +34 -0
- package/lib/tools/issue.d.ts +17 -1
- package/lib/tools/issue.js +114 -8
- package/lib/tools/label.d.ts +80 -0
- package/lib/tools/label.js +237 -0
- package/lib/tools/pr.d.ts +12 -0
- package/lib/tools/pr.js +96 -4
- package/lib/tools/repo.d.ts +8 -0
- package/lib/tools/repo.js +127 -3
- package/lib/tools/ruleset.d.ts +80 -0
- package/lib/tools/ruleset.js +177 -0
- package/lib/tools/run.d.ts +8 -0
- package/lib/tools/run.js +104 -3
- package/lib/tools/secret.d.ts +5 -0
- package/lib/tools/secret.js +108 -23
- package/lib/tools/status.d.ts +39 -0
- package/lib/tools/status.js +53 -0
- package/lib/types.d.ts +112 -0
- package/lib/ui/auth-guide-card.d.ts +4 -0
- package/lib/ui/auth-guide-card.js +161 -0
- package/lib/ui/branch-selector.d.ts +10 -0
- package/lib/ui/branch-selector.js +163 -0
- package/lib/ui/commit-modal.d.ts +8 -0
- package/lib/ui/commit-modal.js +89 -0
- package/lib/ui/icons.d.ts +7 -0
- package/lib/ui/icons.js +53 -0
- package/lib/ui/network-banner.d.ts +10 -0
- package/lib/ui/network-banner.js +54 -0
- package/lib/ui/storage.d.ts +26 -0
- package/lib/ui/storage.js +134 -0
- package/lib/ui/styles.d.ts +6 -0
- package/lib/ui/styles.js +1285 -0
- package/lib/ui/sync-settings-modal.d.ts +11 -0
- package/lib/ui/sync-settings-modal.js +350 -0
- package/lib/ui/sync-status-badge.d.ts +8 -0
- package/lib/ui/sync-status-badge.js +64 -0
- package/lib/ui/upgrade-card.d.ts +7 -0
- package/lib/ui/upgrade-card.js +197 -0
- package/lib/ui/utils.d.ts +10 -0
- package/lib/ui/utils.js +66 -0
- package/lib/ui/workspace-panel.d.ts +4 -0
- package/lib/ui/workspace-panel.js +464 -0
- package/lib/version.d.ts +1 -1
- package/lib/version.js +1 -1
- package/package.json +1 -1
|
@@ -17,6 +17,7 @@ export function registerOverviewRoutes(deps, executor, cacheMap, globalCache, ch
|
|
|
17
17
|
const url = new URL(req.url || '', 'http://127.0.0.1');
|
|
18
18
|
let cwd = url.searchParams.get('cwd');
|
|
19
19
|
const sessionId = url.searchParams.get('sessionId');
|
|
20
|
+
const force = url.searchParams.get('force') === 'true';
|
|
20
21
|
// 会话 ID → 物理路径,以及「最近活跃工作区」兜底,统一由 workspace 模块提供
|
|
21
22
|
if (!cwd && sessionId) {
|
|
22
23
|
cwd = findWorkspacePathBySession(sessionId) || null;
|
|
@@ -29,7 +30,7 @@ export function registerOverviewRoutes(deps, executor, cacheMap, globalCache, ch
|
|
|
29
30
|
}
|
|
30
31
|
const now = Date.now();
|
|
31
32
|
const cachedData = cacheMap.get(cwd, now);
|
|
32
|
-
if (cachedData) {
|
|
33
|
+
if (cachedData && !force) {
|
|
33
34
|
sendJson(res, 200, {
|
|
34
35
|
ok: true,
|
|
35
36
|
data: { ...cachedData, updateInfo: checker.getQuickStatus() },
|
|
@@ -39,10 +40,31 @@ export function registerOverviewRoutes(deps, executor, cacheMap, globalCache, ch
|
|
|
39
40
|
return;
|
|
40
41
|
}
|
|
41
42
|
const auth = await executor.checkAuth(cwd);
|
|
42
|
-
|
|
43
|
+
// 先行检查当前工作区是否真实包含 .git,无 .git 铁证判定为未关联仓库
|
|
44
|
+
const isGitRepo = hasGitDir(cwd);
|
|
45
|
+
const repo = isGitRepo ? await executor.getRepoMetadata(cwd) : null;
|
|
46
|
+
// 若当前工作区未关联 Git 仓库,直接返回纯净空状态,坚决不拉取远端数据
|
|
47
|
+
if (!isGitRepo || !repo) {
|
|
48
|
+
const emptyData = {
|
|
49
|
+
auth,
|
|
50
|
+
repo: undefined,
|
|
51
|
+
pullRequests: [],
|
|
52
|
+
issues: [],
|
|
53
|
+
issueStats: { total: 0, reported: 0, assigned: 0, created: 0 },
|
|
54
|
+
runs: [],
|
|
55
|
+
lastUpdated: new Date().toISOString(),
|
|
56
|
+
updateInfo: checker.getQuickStatus(),
|
|
57
|
+
};
|
|
58
|
+
cacheMap.set(cwd, emptyData, now);
|
|
59
|
+
sendJson(res, 200, { ok: true, data: emptyData, fromCache: false, cwd });
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
43
62
|
let pullRequests = [];
|
|
44
63
|
let issues = [];
|
|
45
64
|
let runs = [];
|
|
65
|
+
// 面板增强信息载体(git 状态/Release 锚定/分支 PR——全部可选字段,
|
|
66
|
+
// 进缓存与快照时旧消费方零破坏,渲染层 ?. 兜底)
|
|
67
|
+
const freshExtras = {};
|
|
46
68
|
if (repo && auth.loggedIn) {
|
|
47
69
|
// 并发拉取当前文件夹仓库的 PRs、Issues 和 Runs
|
|
48
70
|
const [prsRes, issuesRes, runsRes] = await Promise.all([
|
|
@@ -50,6 +72,52 @@ export function registerOverviewRoutes(deps, executor, cacheMap, globalCache, ch
|
|
|
50
72
|
executor.run(['issue', 'list', '--json', 'number,title,state,author,labels,assignees,url,updatedAt', '-L', '10'], { cwd, timeoutMs: 8_000 }),
|
|
51
73
|
executor.run(['run', 'list', '--json', 'databaseId,name,status,conclusion,event,headBranch,url,createdAt', '-L', '5'], { cwd, timeoutMs: 8_000 }),
|
|
52
74
|
]);
|
|
75
|
+
// 核心数据拉取故障检测:若拉取核心业务数据遭遇失败(网络超时/断网/接口报错),
|
|
76
|
+
// 绝对不能把失败伪装成「0 个 PR、0 个 Issue」并假装成功返回!
|
|
77
|
+
const failures = [];
|
|
78
|
+
if (!prsRes.ok)
|
|
79
|
+
failures.push(`PR 获取失败: ${prsRes.error || '网络或接口异常'}`);
|
|
80
|
+
if (!issuesRes.ok)
|
|
81
|
+
failures.push(`Issue 获取失败: ${issuesRes.error || '网络或接口异常'}`);
|
|
82
|
+
if (!runsRes.ok)
|
|
83
|
+
failures.push(`Actions 获取失败: ${runsRes.error || '网络或接口异常'}`);
|
|
84
|
+
if (failures.length > 0) {
|
|
85
|
+
const failureReason = failures.join(';');
|
|
86
|
+
// 若此前内存有缓存,保全已有缓存内容,附加 networkError 与 partialFailure
|
|
87
|
+
const fallbackCached = cacheMap.getFallback(cwd) || cacheMap.get(cwd, now);
|
|
88
|
+
if (fallbackCached && (fallbackCached.pullRequests.length > 0 || fallbackCached.issues.length > 0 || fallbackCached.runs.length > 0)) {
|
|
89
|
+
sendJson(res, 200, {
|
|
90
|
+
ok: true,
|
|
91
|
+
fromCache: true,
|
|
92
|
+
partialFailure: true,
|
|
93
|
+
error: failureReason,
|
|
94
|
+
data: {
|
|
95
|
+
...fallbackCached,
|
|
96
|
+
networkError: true,
|
|
97
|
+
networkErrorReason: failureReason,
|
|
98
|
+
auth: { ...fallbackCached.auth, networkError: true, networkErrorReason: failureReason },
|
|
99
|
+
},
|
|
100
|
+
cwd,
|
|
101
|
+
});
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
const freshData = {
|
|
106
|
+
auth: { ...auth, networkError: true, networkErrorReason: failureReason },
|
|
107
|
+
repo: repo || undefined,
|
|
108
|
+
pullRequests: [],
|
|
109
|
+
issues: [],
|
|
110
|
+
issueStats: { total: 0, reported: 0, assigned: 0, created: 0 },
|
|
111
|
+
runs: [],
|
|
112
|
+
networkError: true,
|
|
113
|
+
networkErrorReason: failureReason,
|
|
114
|
+
lastUpdated: new Date().toISOString(),
|
|
115
|
+
updateInfo: checker.getQuickStatus(),
|
|
116
|
+
};
|
|
117
|
+
sendJson(res, 200, { ok: true, partialFailure: true, error: failureReason, data: freshData, cwd });
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
53
121
|
if (prsRes.ok && Array.isArray(prsRes.data))
|
|
54
122
|
pullRequests = prsRes.data;
|
|
55
123
|
if (issuesRes.ok && Array.isArray(issuesRes.data)) {
|
|
@@ -65,6 +133,36 @@ export function registerOverviewRoutes(deps, executor, cacheMap, globalCache, ch
|
|
|
65
133
|
}
|
|
66
134
|
if (runsRes.ok && Array.isArray(runsRes.data))
|
|
67
135
|
runs = runsRes.data;
|
|
136
|
+
// 面板信息增强(三路并行,全部失败静默——可选装饰信息不阻塞主流程):
|
|
137
|
+
// 1. git 本地状态:纯本地毫秒级(remote-tracking 快照),零网络
|
|
138
|
+
// 2. Release 锚定:gh release view,无 Release 仓库是常态非错误
|
|
139
|
+
// 3. 当前分支↔PR 关联:pr status -c,无 PR 分支同样静默
|
|
140
|
+
const [gitStatus, releaseInfo, branchPr] = await Promise.all([
|
|
141
|
+
executor.getLocalGitStatus(cwd).catch(() => null),
|
|
142
|
+
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),
|
|
143
|
+
executor.run(['pr', 'status', '-c', '--json', 'number,title,state,url,isDraft', '-R', repo.nameWithOwner], { cwd, timeoutMs: 8_000 }).then((r) => {
|
|
144
|
+
// 实测键集:{ createdBy: [...], needsReview: [...] }——「当前分支
|
|
145
|
+
// 关联 PR」按优先级取 createdBy(自己发起)→ needsReview(待审)。
|
|
146
|
+
// 不传 rawText:executor 对含 --json 的命令自动解析出 data——
|
|
147
|
+
// rawText 模式 data 恒 undefined 会让本功能静默失效(审查抓到的真实 Bug)
|
|
148
|
+
if (!r.ok || !r.data)
|
|
149
|
+
return null;
|
|
150
|
+
const createdBy = Array.isArray(r.data?.createdBy) ? r.data.createdBy : [];
|
|
151
|
+
const needsReview = Array.isArray(r.data?.needsReview) ? r.data.needsReview : [];
|
|
152
|
+
return createdBy[0] || needsReview[0] || null;
|
|
153
|
+
}).catch(() => null),
|
|
154
|
+
]);
|
|
155
|
+
freshExtras.gitStatus = gitStatus;
|
|
156
|
+
freshExtras.releaseInfo = releaseInfo;
|
|
157
|
+
freshExtras.branchPr = branchPr;
|
|
158
|
+
// 4. git 提交记录三段(领先/落后/最近):依赖 gitStatus.hasUpstream 守卫,
|
|
159
|
+
// 在并行波次之后串行补(本地毫秒级,无感知延迟)——放进第一波会因
|
|
160
|
+
// Promise.all 参数在调用时求值而引用未解构的 gitStatus(恒 undefined)
|
|
161
|
+
freshExtras.commitLog = await executor
|
|
162
|
+
.getGitCommitLog(cwd, gitStatus ? gitStatus.hasUpstream : false)
|
|
163
|
+
.catch(() => null);
|
|
164
|
+
// 5. 分支列表(纯本地毫秒级):供右侧栏分支选择器 0ms 秒开,消除独立请求延迟与 404 风险
|
|
165
|
+
freshExtras.branches = await executor.getGitBranches(cwd).catch(() => null);
|
|
68
166
|
}
|
|
69
167
|
const issueStats = calculateIssueStats(issues);
|
|
70
168
|
const freshData = {
|
|
@@ -76,6 +174,7 @@ export function registerOverviewRoutes(deps, executor, cacheMap, globalCache, ch
|
|
|
76
174
|
runs,
|
|
77
175
|
lastUpdated: new Date().toISOString(),
|
|
78
176
|
updateInfo: checker.getQuickStatus(),
|
|
177
|
+
...freshExtras,
|
|
79
178
|
};
|
|
80
179
|
cacheMap.set(cwd, freshData, now);
|
|
81
180
|
sendJson(res, 200, { ok: true, data: freshData, fromCache: false, cwd });
|
|
@@ -124,7 +223,7 @@ export function registerOverviewRoutes(deps, executor, cacheMap, globalCache, ch
|
|
|
124
223
|
const issueFields = 'number,title,repository,author,assignees,labels,url,updatedAt';
|
|
125
224
|
const queryResults = await Promise.allSettled([
|
|
126
225
|
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 }),
|
|
226
|
+
executor.run(['search', 'prs', '--author=@me', '--state=open', '--limit', '10', '--json', 'number,title,repository,url,updatedAt,isDraft'], { timeoutMs: 8_000 }),
|
|
128
227
|
executor.run(['search', 'issues', '--owner=@me', '--state=open', '--sort=updated', '--limit', '15', '--json', issueFields], { timeoutMs: 8_000 }),
|
|
129
228
|
executor.run(['search', 'issues', '--assignee=@me', '--state=open', '--sort=updated', '--limit', '15', '--json', issueFields], { timeoutMs: 8_000 }),
|
|
130
229
|
executor.run(['search', 'issues', '--author=@me', '--state=open', '--sort=updated', '--limit', '15', '--json', issueFields], { timeoutMs: 8_000 }),
|
|
@@ -134,6 +233,31 @@ export function registerOverviewRoutes(deps, executor, cacheMap, globalCache, ch
|
|
|
134
233
|
const ownerIssuesRes = queryResults[2].status === 'fulfilled' ? queryResults[2].value : { ok: false };
|
|
135
234
|
const assignedIssuesRes = queryResults[3].status === 'fulfilled' ? queryResults[3].value : { ok: false };
|
|
136
235
|
const authorIssuesRes = queryResults[4].status === 'fulfilled' ? queryResults[4].value : { ok: false };
|
|
236
|
+
// 检测全局拉取是否发生网络故障,绝不把网络失败当作「0 仓库、0 PR」伪造成功
|
|
237
|
+
const globalFailures = [];
|
|
238
|
+
if (!reposRes.ok)
|
|
239
|
+
globalFailures.push(`仓库列表拉取失败: ${reposRes.error || '网络或接口异常'}`);
|
|
240
|
+
if (!prsRes.ok)
|
|
241
|
+
globalFailures.push(`个人 PR 拉取失败: ${prsRes.error || '网络或接口异常'}`);
|
|
242
|
+
if (globalFailures.length > 0) {
|
|
243
|
+
const globalNetReason = globalFailures.join(';');
|
|
244
|
+
const fallbackGlobal = globalCache.getFallback('global') || globalCache.get('global', now);
|
|
245
|
+
if (fallbackGlobal) {
|
|
246
|
+
sendJson(res, 200, {
|
|
247
|
+
ok: true,
|
|
248
|
+
fromCache: true,
|
|
249
|
+
partialFailure: true,
|
|
250
|
+
error: globalNetReason,
|
|
251
|
+
data: {
|
|
252
|
+
...fallbackGlobal,
|
|
253
|
+
networkError: true,
|
|
254
|
+
networkErrorReason: globalNetReason,
|
|
255
|
+
auth: { ...fallbackGlobal.auth, networkError: true, networkErrorReason: globalNetReason },
|
|
256
|
+
},
|
|
257
|
+
});
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
137
261
|
if (reposRes.ok && Array.isArray(reposRes.data)) {
|
|
138
262
|
userRepos = reposRes.data.map((r) => ({
|
|
139
263
|
name: r.name || '',
|
|
@@ -195,6 +319,9 @@ export function registerOverviewRoutes(deps, executor, cacheMap, globalCache, ch
|
|
|
195
319
|
// 每次网络往返都要秒级,5 个工作区就足以把该端点拖到 20 秒。
|
|
196
320
|
workspaceMatrix = await mapLimit(wsEntries, WORKSPACE_PROBE_CONCURRENCY, async (ws) => {
|
|
197
321
|
const hasGit = hasGitDir(ws.path);
|
|
322
|
+
// git 状态与身份识别同为纯本地探测(remote-tracking 快照零网络),
|
|
323
|
+
// 挂进同一 mapLimit 有界并发——不新增并发维度,不碰性能红线
|
|
324
|
+
const gitStatus = hasGit ? await executor.getLocalGitStatus(ws.path).catch(() => null) : null;
|
|
198
325
|
return {
|
|
199
326
|
id: ws.id,
|
|
200
327
|
title: ws.title || basenameSafe(ws.path),
|
|
@@ -202,6 +329,7 @@ export function registerOverviewRoutes(deps, executor, cacheMap, globalCache, ch
|
|
|
202
329
|
hasGit,
|
|
203
330
|
repo: hasGit ? executor.getRepoIdentityFromLocal(ws.path) || undefined : undefined,
|
|
204
331
|
sessionCount: ws.sessionIds.length,
|
|
332
|
+
...(gitStatus ? { gitStatus } : {}),
|
|
205
333
|
};
|
|
206
334
|
});
|
|
207
335
|
}
|
|
@@ -224,4 +352,71 @@ export function registerOverviewRoutes(deps, executor, cacheMap, globalCache, ch
|
|
|
224
352
|
}
|
|
225
353
|
},
|
|
226
354
|
});
|
|
355
|
+
// 3. 单条提交详情(Git 记录页点击行的惰性查询——弹窗打开时才发起,
|
|
356
|
+
// 不进 overview 缓存:低频单查 + git show 本地毫秒级,无缓存必要)
|
|
357
|
+
bindRoute({
|
|
358
|
+
kind: 'exact',
|
|
359
|
+
path: '/api/github/commit-detail',
|
|
360
|
+
handler: async (req, res) => {
|
|
361
|
+
if (req.method !== 'GET') {
|
|
362
|
+
sendJson(res, 405, { ok: false, message: '仅支持 GET 请求' });
|
|
363
|
+
return;
|
|
364
|
+
}
|
|
365
|
+
try {
|
|
366
|
+
const url = new URL(req.url || '', 'http://127.0.0.1');
|
|
367
|
+
let cwd = url.searchParams.get('cwd') || undefined;
|
|
368
|
+
const sessionId = url.searchParams.get('sessionId');
|
|
369
|
+
if (!cwd && sessionId) {
|
|
370
|
+
cwd = findWorkspacePathBySession(sessionId) || undefined;
|
|
371
|
+
}
|
|
372
|
+
if (!cwd) {
|
|
373
|
+
cwd = findMostRecentWorkspacePath() || undefined;
|
|
374
|
+
}
|
|
375
|
+
const sha = url.searchParams.get('sha') || '';
|
|
376
|
+
// sha 形态校验双保险(executor 内还有一道):路由层先拦明显恶意值
|
|
377
|
+
if (!/^[0-9a-f]{4,40}$/i.test(sha)) {
|
|
378
|
+
sendJson(res, 400, { ok: false, error: '非法 sha(仅接受 4-40 位十六进制)' });
|
|
379
|
+
return;
|
|
380
|
+
}
|
|
381
|
+
const detail = await executor.getGitCommitDetail(cwd, sha);
|
|
382
|
+
if (detail === null) {
|
|
383
|
+
sendJson(res, 404, { ok: false, error: '未找到该提交(可能不在当前仓库)' });
|
|
384
|
+
return;
|
|
385
|
+
}
|
|
386
|
+
sendJson(res, 200, { ok: true, detail });
|
|
387
|
+
}
|
|
388
|
+
catch (err) {
|
|
389
|
+
sendJson(res, 500, { ok: false, error: err.message || '查询提交详情失败' });
|
|
390
|
+
}
|
|
391
|
+
},
|
|
392
|
+
});
|
|
393
|
+
// 4. 分页获取最近提交记录(Git 记录页下滑触底自动加载或点击加载更多)
|
|
394
|
+
bindRoute({
|
|
395
|
+
kind: 'exact',
|
|
396
|
+
path: '/api/github/git-log',
|
|
397
|
+
handler: async (req, res) => {
|
|
398
|
+
if (req.method !== 'GET') {
|
|
399
|
+
sendJson(res, 405, { ok: false, message: '仅支持 GET 请求' });
|
|
400
|
+
return;
|
|
401
|
+
}
|
|
402
|
+
try {
|
|
403
|
+
const url = new URL(req.url || '', 'http://127.0.0.1');
|
|
404
|
+
let cwd = url.searchParams.get('cwd') || undefined;
|
|
405
|
+
const sessionId = url.searchParams.get('sessionId');
|
|
406
|
+
if (!cwd && sessionId) {
|
|
407
|
+
cwd = findWorkspacePathBySession(sessionId) || undefined;
|
|
408
|
+
}
|
|
409
|
+
if (!cwd) {
|
|
410
|
+
cwd = findMostRecentWorkspacePath() || undefined;
|
|
411
|
+
}
|
|
412
|
+
const limit = Math.min(Math.max(1, Number(url.searchParams.get('limit')) || 15), 50);
|
|
413
|
+
const skip = Math.max(0, Number(url.searchParams.get('skip')) || 0);
|
|
414
|
+
const result = await executor.getGitRecentLog(cwd, limit, skip);
|
|
415
|
+
sendJson(res, 200, { ok: true, data: result });
|
|
416
|
+
}
|
|
417
|
+
catch (err) {
|
|
418
|
+
sendJson(res, 500, { ok: false, error: err.message || '获取提交记录失败' });
|
|
419
|
+
}
|
|
420
|
+
},
|
|
421
|
+
});
|
|
227
422
|
}
|
package/lib/cache.d.ts
CHANGED
|
@@ -8,6 +8,8 @@ export declare class TtlCache<T> {
|
|
|
8
8
|
private ttlMs;
|
|
9
9
|
private maxEntries;
|
|
10
10
|
private store;
|
|
11
|
+
/** 容灾备份底座:记录最后一次成功的有效快照,不受 TTL 过期或主动 refresh 清空的影响 */
|
|
12
|
+
private fallbackStore;
|
|
11
13
|
/**
|
|
12
14
|
* @param ttlMs 命中有效期;由面向用户的 cacheTtlMs 配置驱动
|
|
13
15
|
* @param maxEntries 容量上限。键通常是工作区路径,一次会话就可能引入若干键,
|
|
@@ -16,6 +18,8 @@ export declare class TtlCache<T> {
|
|
|
16
18
|
constructor(ttlMs: number, maxEntries?: number);
|
|
17
19
|
get(key: string, now: number): T | undefined;
|
|
18
20
|
set(key: string, data: T, now: number): void;
|
|
21
|
+
/** 获取最后一次有效成功的容灾底座数据(专供网络故障、超时时的 Fail-Safe 保全) */
|
|
22
|
+
getFallback(key: string): T | undefined;
|
|
19
23
|
get size(): number;
|
|
20
24
|
clear(): void;
|
|
21
25
|
}
|
package/lib/cache.js
CHANGED
|
@@ -8,6 +8,8 @@ export class TtlCache {
|
|
|
8
8
|
ttlMs;
|
|
9
9
|
maxEntries;
|
|
10
10
|
store = new Map();
|
|
11
|
+
/** 容灾备份底座:记录最后一次成功的有效快照,不受 TTL 过期或主动 refresh 清空的影响 */
|
|
12
|
+
fallbackStore = new Map();
|
|
11
13
|
/**
|
|
12
14
|
* @param ttlMs 命中有效期;由面向用户的 cacheTtlMs 配置驱动
|
|
13
15
|
* @param maxEntries 容量上限。键通常是工作区路径,一次会话就可能引入若干键,
|
|
@@ -32,17 +34,29 @@ export class TtlCache {
|
|
|
32
34
|
}
|
|
33
35
|
set(key, data, now) {
|
|
34
36
|
this.store.set(key, { data, time: now });
|
|
37
|
+
this.fallbackStore.set(key, data);
|
|
35
38
|
while (this.store.size > this.maxEntries) {
|
|
36
39
|
const oldest = this.store.keys().next();
|
|
37
40
|
if (oldest.done)
|
|
38
41
|
break;
|
|
39
42
|
this.store.delete(oldest.value);
|
|
40
43
|
}
|
|
44
|
+
while (this.fallbackStore.size > this.maxEntries) {
|
|
45
|
+
const oldest = this.fallbackStore.keys().next();
|
|
46
|
+
if (oldest.done)
|
|
47
|
+
break;
|
|
48
|
+
this.fallbackStore.delete(oldest.value);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
/** 获取最后一次有效成功的容灾底座数据(专供网络故障、超时时的 Fail-Safe 保全) */
|
|
52
|
+
getFallback(key) {
|
|
53
|
+
return this.fallbackStore.get(key);
|
|
41
54
|
}
|
|
42
55
|
get size() {
|
|
43
56
|
return this.store.size;
|
|
44
57
|
}
|
|
45
58
|
clear() {
|
|
59
|
+
// 清空活动缓存以迫使后续请求重新发起网络拉取,但保留 fallbackStore 用于网络故障容灾
|
|
46
60
|
this.store.clear();
|
|
47
61
|
}
|
|
48
62
|
}
|
package/lib/client.d.ts
CHANGED
|
@@ -8,4 +8,16 @@ declare global {
|
|
|
8
8
|
};
|
|
9
9
|
}
|
|
10
10
|
}
|
|
11
|
-
|
|
11
|
+
import { formatRelativeTime, rollupCiStatus, reviewDecisionBadge, eventLabel } from './ui/utils.js';
|
|
12
|
+
import { GITHUB_PANEL_CSS, injectStyles } from './ui/styles.js';
|
|
13
|
+
import { renderOctocat, renderGitIcon, renderOctocatBadge, renderPluginBadge } from './ui/icons.js';
|
|
14
|
+
import { getLocalSyncSettings, saveLocalSyncSettings, getStoredGlobalOverview, saveStoredGlobalOverview, clearStoredGlobalOverview, getCacheStorageMeta, getRightbarStorageKey, getStoredRightbarSnapshot, saveStoredRightbarData } from './ui/storage.js';
|
|
15
|
+
import { BranchSelectorDropdown } from './ui/branch-selector.js';
|
|
16
|
+
import { AuthGuideCard } from './ui/auth-guide-card.js';
|
|
17
|
+
import { UpgradeNotificationCard } from './ui/upgrade-card.js';
|
|
18
|
+
import { NetworkAlertBanner } from './ui/network-banner.js';
|
|
19
|
+
import { SyncStatusBadge } from './ui/sync-status-badge.js';
|
|
20
|
+
import { CommitDetailModal } from './ui/commit-modal.js';
|
|
21
|
+
import { GlobalSyncSettingsModal } from './ui/sync-settings-modal.js';
|
|
22
|
+
import { GithubWorkspacePanel } from './ui/workspace-panel.js';
|
|
23
|
+
export { formatRelativeTime, rollupCiStatus, reviewDecisionBadge, eventLabel, GITHUB_PANEL_CSS, injectStyles, renderOctocat, renderGitIcon, renderOctocatBadge, renderPluginBadge, getLocalSyncSettings, saveLocalSyncSettings, getStoredGlobalOverview, saveStoredGlobalOverview, clearStoredGlobalOverview, getCacheStorageMeta, getRightbarStorageKey, getStoredRightbarSnapshot, saveStoredRightbarData, BranchSelectorDropdown, AuthGuideCard, UpgradeNotificationCard, NetworkAlertBanner, SyncStatusBadge, CommitDetailModal, GlobalSyncSettingsModal, GithubWorkspacePanel, };
|