dsh-github-flow 0.1.4 → 0.1.5
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 +27 -2
- package/README.md +26 -2
- package/docs/images/1.png +0 -0
- package/docs/images/2.png +0 -0
- package/lib/api/routes.d.ts +6 -1
- package/lib/api/routes.js +147 -94
- package/lib/cache.d.ts +21 -0
- package/lib/cache.js +48 -0
- package/lib/client.js +23 -5
- package/lib/commands/gh.js +3 -1
- package/lib/executor.d.ts +30 -1
- package/lib/executor.js +167 -50
- package/lib/github-service.d.ts +26 -0
- package/lib/github-service.js +1 -0
- package/lib/main.d.ts +2 -0
- package/lib/main.js +9 -1
- package/lib/tools/api.d.ts +4 -0
- package/lib/tools/api.js +23 -4
- package/lib/tools/pr.d.ts +12 -0
- package/lib/tools/pr.js +30 -3
- package/lib/types.d.ts +4 -0
- package/lib/workspace.d.ts +43 -0
- package/lib/workspace.js +136 -0
- package/package.json +2 -1
package/lib/workspace.js
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 工作区解析的单一数据源。
|
|
3
|
+
*
|
|
4
|
+
* 为什么独立成模块:`~/.dsh/storages/workspace.json` 的读取与「会话 ID → 物理路径」的
|
|
5
|
+
* 反查逻辑原先在 executor.ts 与 api/routes.ts 里各写了一份(routes.ts 内还写了两遍),
|
|
6
|
+
* 三份实现的字段兜底行为已有细微差异。同一份事实在多个地方各自解析,迟早会出现
|
|
7
|
+
* 「工具看到的工作区」与「面板显示的工作区」不一致,因此收敛到这里。
|
|
8
|
+
*
|
|
9
|
+
* 本模块刻意不依赖 GhExecutor,只做纯文件读取,便于单元测试与复用。
|
|
10
|
+
*/
|
|
11
|
+
import fs from 'node:fs';
|
|
12
|
+
import os from 'node:os';
|
|
13
|
+
import path from 'node:path';
|
|
14
|
+
/** 缓存时长:workspace.json 变更频率低,但面板会反复读取,缓存可显著降低磁盘 IO 与解析开销 */
|
|
15
|
+
const WORKSPACE_CACHE_TTL_MS = 5_000;
|
|
16
|
+
let cache = null;
|
|
17
|
+
/** 仅供测试与配置变更后失效使用 */
|
|
18
|
+
export function resetWorkspaceCache() {
|
|
19
|
+
cache = null;
|
|
20
|
+
}
|
|
21
|
+
export function workspaceJsonPath() {
|
|
22
|
+
return path.join(os.homedir(), '.dsh', 'storages', 'workspace.json');
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* 读取全部工作区记录。
|
|
26
|
+
* 返回空数组表示「文件不存在」,抛错表示「文件存在但无法解析」——调用方必须区分这两种情况,
|
|
27
|
+
* 否则 workspace.json 损坏时会显示成「你没有工作区」,把故障伪装成正常空状态。
|
|
28
|
+
*/
|
|
29
|
+
export function readWorkspaces(options = {}) {
|
|
30
|
+
const now = Date.now();
|
|
31
|
+
if (!options.bypassCache && cache && now - cache.at < WORKSPACE_CACHE_TTL_MS) {
|
|
32
|
+
return cache.records;
|
|
33
|
+
}
|
|
34
|
+
const file = workspaceJsonPath();
|
|
35
|
+
if (!fs.existsSync(file)) {
|
|
36
|
+
cache = { at: now, records: [] };
|
|
37
|
+
return [];
|
|
38
|
+
}
|
|
39
|
+
const parsed = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
40
|
+
const raw = parsed?.tables?.workspaces ?? {};
|
|
41
|
+
const records = Object.keys(raw).map((id) => {
|
|
42
|
+
const item = raw[id] ?? {};
|
|
43
|
+
return {
|
|
44
|
+
id,
|
|
45
|
+
title: typeof item.title === 'string' ? item.title : '',
|
|
46
|
+
path: typeof item.path === 'string' ? item.path : '',
|
|
47
|
+
sessionIds: Array.isArray(item.sessionIds) ? item.sessionIds : [],
|
|
48
|
+
updatedAt: typeof item.updatedAt === 'string' ? item.updatedAt : '',
|
|
49
|
+
};
|
|
50
|
+
});
|
|
51
|
+
cache = { at: now, records };
|
|
52
|
+
return records;
|
|
53
|
+
}
|
|
54
|
+
/** 容错读取:读取失败时返回空数组并把原因交给调用方,绝不静默吞掉 */
|
|
55
|
+
export function tryReadWorkspaces() {
|
|
56
|
+
try {
|
|
57
|
+
return { workspaces: readWorkspaces() };
|
|
58
|
+
}
|
|
59
|
+
catch (err) {
|
|
60
|
+
return { workspaces: [], error: err?.message || '工作区数据读取失败' };
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* 按会话 ID 反查物理工作区路径。
|
|
65
|
+
* 命中要求路径真实存在——workspace.json 里可能残留已删除的目录。
|
|
66
|
+
*/
|
|
67
|
+
export function findWorkspacePathBySession(sessionId) {
|
|
68
|
+
try {
|
|
69
|
+
const { workspaces } = tryReadWorkspaces();
|
|
70
|
+
for (const ws of workspaces) {
|
|
71
|
+
if (ws.sessionIds.includes(sessionId) && ws.path && fs.existsSync(ws.path)) {
|
|
72
|
+
return ws.path;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
// 会话反查是可选的尽力而为路径,失败时由调用方回退到其他策略
|
|
78
|
+
}
|
|
79
|
+
return undefined;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* 匹配最近活跃的工作区路径。
|
|
83
|
+
* 注意 updatedAt 可能缺失或非 ISO 格式,统一用时间戳比较,无效值视为 0 而非 NaN——
|
|
84
|
+
* 后者会让所有比较都返回 false,导致永远选不中任何工作区。
|
|
85
|
+
*/
|
|
86
|
+
export function findMostRecentWorkspacePath() {
|
|
87
|
+
try {
|
|
88
|
+
const { workspaces } = tryReadWorkspaces();
|
|
89
|
+
let best = null;
|
|
90
|
+
let bestAt = -1;
|
|
91
|
+
for (const ws of workspaces) {
|
|
92
|
+
const at = Date.parse(ws.updatedAt || '');
|
|
93
|
+
const score = Number.isFinite(at) ? at : 0;
|
|
94
|
+
if (score > bestAt && ws.path && fs.existsSync(ws.path)) {
|
|
95
|
+
best = ws;
|
|
96
|
+
bestAt = score;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return best?.path;
|
|
100
|
+
}
|
|
101
|
+
catch {
|
|
102
|
+
return undefined;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
export function hasGitDir(dir) {
|
|
106
|
+
try {
|
|
107
|
+
return fs.existsSync(path.join(dir, '.git'));
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
return false;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* 有界并发 map。
|
|
115
|
+
* 为什么必须有:每个工作区探测最多 spawn 2 个 gh 进程,而 Node 的 libuv 线程池默认只有 4 个
|
|
116
|
+
* 线程可用。工作区一多,无上限的 Promise.all 会把进程全部排进队列,单次 spawn 超时 10s × 排队
|
|
117
|
+
* 造成整体秒级到十几秒的延迟(实测 /api/github/global-overview 曾达 18.9s)。
|
|
118
|
+
* 这里显式限制在途数量,让延迟可预期,而不是随工作区数量线性劣化。
|
|
119
|
+
*/
|
|
120
|
+
export async function mapLimit(items, limit, fn) {
|
|
121
|
+
if (items.length === 0)
|
|
122
|
+
return [];
|
|
123
|
+
const effectiveLimit = Math.max(1, Math.min(limit, items.length));
|
|
124
|
+
const results = new Array(items.length);
|
|
125
|
+
let cursor = 0;
|
|
126
|
+
const workers = Array.from({ length: effectiveLimit }, async () => {
|
|
127
|
+
for (;;) {
|
|
128
|
+
const index = cursor++;
|
|
129
|
+
if (index >= items.length)
|
|
130
|
+
return;
|
|
131
|
+
results[index] = await fn(items[index], index);
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
await Promise.all(workers);
|
|
135
|
+
return results;
|
|
136
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-github-flow",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5",
|
|
4
4
|
"icon": "icon.svg",
|
|
5
5
|
"description": "GitHub Flow: Native GitHub CLI (gh) integration for DeepSeek Harness — Agent Tools, Slash Commands, and Web UI Workspace",
|
|
6
6
|
"type": "module",
|
|
@@ -55,6 +55,7 @@
|
|
|
55
55
|
"registry": "https://registry.npmjs.org/"
|
|
56
56
|
},
|
|
57
57
|
"devDependencies": {
|
|
58
|
+
"@deepseek-ai/cordis": "^4.0.4",
|
|
58
59
|
"@types/node": "^26.6.3",
|
|
59
60
|
"typescript": "^7.0.2"
|
|
60
61
|
}
|