dsh-github-flow 0.1.4 → 0.1.6
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 +33 -3
- package/README.md +32 -3
- package/docs/architecture.md +6 -0
- 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 +182 -94
- package/lib/cache.d.ts +21 -0
- package/lib/cache.js +48 -0
- package/lib/client.js +297 -16
- package/lib/commands/gh.js +3 -1
- package/lib/executor.d.ts +32 -1
- package/lib/executor.js +195 -51
- 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 +15 -0
- package/lib/workspace.d.ts +43 -0
- package/lib/workspace.js +136 -0
- package/package.json +2 -1
package/lib/client.js
CHANGED
|
@@ -633,6 +633,110 @@ body[data-ds-dark-theme] .dsh-github-repo-link {
|
|
|
633
633
|
body[data-ds-dark-theme] .dsh-github-subheading {
|
|
634
634
|
color: var(--dsw-alias-label-primary, #f0f6fc);
|
|
635
635
|
}
|
|
636
|
+
|
|
637
|
+
/* 15. 就绪引导横幅 (Setup Guide Banner) */
|
|
638
|
+
.dsh-github-guide-card {
|
|
639
|
+
padding: 16px;
|
|
640
|
+
border-radius: 8px;
|
|
641
|
+
margin-bottom: 16px;
|
|
642
|
+
border: 1px solid var(--dsw-alias-border-l2, #d0d7de);
|
|
643
|
+
background: var(--dsw-alias-bg-layer-1, #f6f8fa);
|
|
644
|
+
}
|
|
645
|
+
.dsh-github-guide-card.warning {
|
|
646
|
+
border-color: rgba(210, 153, 34, 0.4);
|
|
647
|
+
background: rgba(210, 153, 34, 0.08);
|
|
648
|
+
}
|
|
649
|
+
.dsh-github-guide-card.danger {
|
|
650
|
+
border-color: rgba(248, 81, 73, 0.4);
|
|
651
|
+
background: rgba(248, 81, 73, 0.08);
|
|
652
|
+
}
|
|
653
|
+
.dsh-github-guide-header {
|
|
654
|
+
display: flex;
|
|
655
|
+
align-items: center;
|
|
656
|
+
gap: 8px;
|
|
657
|
+
font-size: 15px;
|
|
658
|
+
font-weight: 600;
|
|
659
|
+
margin-bottom: 6px;
|
|
660
|
+
color: var(--dsw-alias-label-primary, #1f2328);
|
|
661
|
+
}
|
|
662
|
+
body[data-ds-dark-theme] .dsh-github-guide-header {
|
|
663
|
+
color: var(--dsw-alias-label-primary, #f0f6fc);
|
|
664
|
+
}
|
|
665
|
+
.dsh-github-guide-desc {
|
|
666
|
+
font-size: 13px;
|
|
667
|
+
color: var(--dsw-alias-label-secondary, #656d76);
|
|
668
|
+
line-height: 1.5;
|
|
669
|
+
margin-bottom: 12px;
|
|
670
|
+
}
|
|
671
|
+
body[data-ds-dark-theme] .dsh-github-guide-desc {
|
|
672
|
+
color: var(--dsw-alias-label-secondary, #8c8c8c);
|
|
673
|
+
}
|
|
674
|
+
.dsh-github-cmd-box {
|
|
675
|
+
display: flex;
|
|
676
|
+
align-items: center;
|
|
677
|
+
justify-content: space-between;
|
|
678
|
+
background: var(--dsw-alias-bg-base, #ffffff);
|
|
679
|
+
border: 1px solid var(--dsw-alias-border-l2, #d0d7de);
|
|
680
|
+
border-radius: 6px;
|
|
681
|
+
padding: 8px 12px;
|
|
682
|
+
font-family: var(--ds-font-family-code, monospace);
|
|
683
|
+
font-size: 13px;
|
|
684
|
+
color: var(--dsw-alias-label-primary, #1f2328);
|
|
685
|
+
margin-bottom: 12px;
|
|
686
|
+
}
|
|
687
|
+
body[data-ds-dark-theme] .dsh-github-cmd-box {
|
|
688
|
+
background: var(--dsw-alias-bg-layer-2, #161b22);
|
|
689
|
+
border-color: var(--dsw-alias-border-l1, #30363d);
|
|
690
|
+
color: var(--dsw-alias-label-primary, #f0f6fc);
|
|
691
|
+
}
|
|
692
|
+
.dsh-github-copy-btn {
|
|
693
|
+
padding: 4px 10px;
|
|
694
|
+
font-size: 12px;
|
|
695
|
+
border-radius: 4px;
|
|
696
|
+
border: 1px solid var(--dsw-alias-border-l2, #d0d7de);
|
|
697
|
+
background: var(--dsw-alias-bg-layer-1, #f6f8fa);
|
|
698
|
+
color: var(--dsw-alias-label-primary, #1f2328);
|
|
699
|
+
cursor: pointer;
|
|
700
|
+
white-space: nowrap;
|
|
701
|
+
transition: all 0.15s ease;
|
|
702
|
+
}
|
|
703
|
+
body[data-ds-dark-theme] .dsh-github-copy-btn {
|
|
704
|
+
background: var(--dsw-alias-bg-layer-2, #21262d);
|
|
705
|
+
border-color: var(--dsw-alias-border-l1, #30363d);
|
|
706
|
+
color: var(--dsw-alias-label-primary, #c9d1d9);
|
|
707
|
+
}
|
|
708
|
+
.dsh-github-copy-btn:hover {
|
|
709
|
+
background: var(--dsw-alias-interactive-bg-hover, #ebeef1);
|
|
710
|
+
}
|
|
711
|
+
body[data-ds-dark-theme] .dsh-github-copy-btn:hover {
|
|
712
|
+
background: #30363d;
|
|
713
|
+
}
|
|
714
|
+
.dsh-github-copy-btn.copied {
|
|
715
|
+
color: #2da44e !important;
|
|
716
|
+
border-color: #2da44e !important;
|
|
717
|
+
background: rgba(45, 164, 78, 0.15) !important;
|
|
718
|
+
}
|
|
719
|
+
.dsh-github-steps-list {
|
|
720
|
+
font-size: 12px;
|
|
721
|
+
color: var(--dsw-alias-label-secondary, #656d76);
|
|
722
|
+
line-height: 1.6;
|
|
723
|
+
margin: 0 0 12px 18px;
|
|
724
|
+
padding: 0;
|
|
725
|
+
}
|
|
726
|
+
body[data-ds-dark-theme] .dsh-github-steps-list {
|
|
727
|
+
color: var(--dsw-alias-label-secondary, #8c8c8c);
|
|
728
|
+
}
|
|
729
|
+
.dsh-github-guide-actions {
|
|
730
|
+
display: flex;
|
|
731
|
+
gap: 8px;
|
|
732
|
+
flex-wrap: wrap;
|
|
733
|
+
}
|
|
734
|
+
.dsh-github-guide-tip {
|
|
735
|
+
font-size: 11px;
|
|
736
|
+
color: var(--dsw-alias-label-tertiary, #8c8c8c);
|
|
737
|
+
margin-top: 10px;
|
|
738
|
+
line-height: 1.4;
|
|
739
|
+
}
|
|
636
740
|
`;
|
|
637
741
|
function injectStyles() {
|
|
638
742
|
var styleId = "dsh-plugin-github-styles";
|
|
@@ -672,6 +776,120 @@ body[data-ds-dark-theme] .dsh-github-subheading {
|
|
|
672
776
|
}
|
|
673
777
|
}, renderOctocat(Math.round(size * 0.58), "#ffffff"));
|
|
674
778
|
}
|
|
779
|
+
// 认证与安装引导卡片组件(响应式自适应中央大面板与右侧栏)
|
|
780
|
+
function AuthGuideCard(props) {
|
|
781
|
+
var auth = props ? props.auth : null;
|
|
782
|
+
var onRefresh = props ? props.onRefresh : () => { };
|
|
783
|
+
var isRightbar = Boolean(props && props.isRightbar);
|
|
784
|
+
var [copied, setCopied] = React.useState(false);
|
|
785
|
+
var [launching, setLaunching] = React.useState(false);
|
|
786
|
+
var [toastMsg, setToastMsg] = React.useState(null);
|
|
787
|
+
var notInstalled = !auth?.installed;
|
|
788
|
+
var guide = auth?.installGuide;
|
|
789
|
+
var defaultCmd = notInstalled
|
|
790
|
+
? (guide?.command || "winget install --id GitHub.cli")
|
|
791
|
+
: "gh auth login";
|
|
792
|
+
var copyCmd = (cmd) => {
|
|
793
|
+
try {
|
|
794
|
+
if (navigator && navigator.clipboard && navigator.clipboard.writeText) {
|
|
795
|
+
navigator.clipboard.writeText(cmd);
|
|
796
|
+
}
|
|
797
|
+
else {
|
|
798
|
+
var input = document.createElement("textarea");
|
|
799
|
+
input.value = cmd;
|
|
800
|
+
document.body.appendChild(input);
|
|
801
|
+
input.select();
|
|
802
|
+
document.execCommand("copy");
|
|
803
|
+
document.body.removeChild(input);
|
|
804
|
+
}
|
|
805
|
+
setCopied(true);
|
|
806
|
+
setTimeout(() => setCopied(false), 2000);
|
|
807
|
+
}
|
|
808
|
+
catch (e) {
|
|
809
|
+
console.warn("复制失败:", e);
|
|
810
|
+
}
|
|
811
|
+
};
|
|
812
|
+
var launchTerminal = async (cmd) => {
|
|
813
|
+
setLaunching(true);
|
|
814
|
+
setToastMsg(null);
|
|
815
|
+
try {
|
|
816
|
+
var res = await fetch("/api/github/action", {
|
|
817
|
+
method: "POST",
|
|
818
|
+
headers: { "Content-Type": "application/json" },
|
|
819
|
+
body: JSON.stringify({ action: "open_terminal", command: cmd })
|
|
820
|
+
});
|
|
821
|
+
var json = await res.json();
|
|
822
|
+
if (json.ok) {
|
|
823
|
+
setToastMsg("已唤起系统终端,请在弹出窗口中完成操作");
|
|
824
|
+
setTimeout(() => setToastMsg(null), 4000);
|
|
825
|
+
}
|
|
826
|
+
else {
|
|
827
|
+
setToastMsg(json.message || "唤起失败,请在终端手动运行");
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
catch (e) {
|
|
831
|
+
setToastMsg("无法连接后台服务,请在终端手动运行");
|
|
832
|
+
}
|
|
833
|
+
finally {
|
|
834
|
+
setLaunching(false);
|
|
835
|
+
}
|
|
836
|
+
};
|
|
837
|
+
var openDownload = async () => {
|
|
838
|
+
var url = guide?.downloadUrl || "https://cli.github.com/";
|
|
839
|
+
try {
|
|
840
|
+
await fetch("/api/github/action", {
|
|
841
|
+
method: "POST",
|
|
842
|
+
headers: { "Content-Type": "application/json" },
|
|
843
|
+
body: JSON.stringify({ action: "open_browser", url })
|
|
844
|
+
});
|
|
845
|
+
}
|
|
846
|
+
catch (e) {
|
|
847
|
+
window.open(url, "_blank", "noopener,noreferrer");
|
|
848
|
+
}
|
|
849
|
+
};
|
|
850
|
+
var h = React.createElement;
|
|
851
|
+
return h("div", {
|
|
852
|
+
className: "dsh-github-guide-card " + (notInstalled ? "danger" : "warning"),
|
|
853
|
+
style: isRightbar ? { padding: "12px", marginBottom: "12px" } : undefined
|
|
854
|
+
},
|
|
855
|
+
// 头部
|
|
856
|
+
h("div", { className: "dsh-github-guide-header" }, notInstalled ? "⚠️ 未检测到 GitHub CLI (gh)" : "🔑 GitHub 账号尚未登录", h("span", { className: "dsh-github-badge " + (notInstalled ? "dsh-badge-red" : "dsh-badge-yellow") }, notInstalled ? "未安装" : "未认证")),
|
|
857
|
+
// 描述
|
|
858
|
+
h("div", { className: "dsh-github-guide-desc" }, notInstalled
|
|
859
|
+
? (guide?.description || "DSH GitHub Flow 插件依赖官方 GitHub CLI 工具与云端进行安全交互,当前系统 PATH 中未检测到该工具。")
|
|
860
|
+
: "检测到本机已安装 GitHub CLI,请完成账号授权以解锁仓库管理、PR 审查和 Issues 待办。"),
|
|
861
|
+
// 命令代码展示框
|
|
862
|
+
h("div", { className: "dsh-github-cmd-box" }, h("code", null, defaultCmd), h("button", {
|
|
863
|
+
className: "dsh-github-copy-btn " + (copied ? "copied" : ""),
|
|
864
|
+
onClick: () => copyCmd(defaultCmd),
|
|
865
|
+
title: "复制命令到剪贴板"
|
|
866
|
+
}, copied ? "✓ 已复制" : "📋 复制命令")),
|
|
867
|
+
// 步骤提示
|
|
868
|
+
!notInstalled && !isRightbar
|
|
869
|
+
? h("ol", { className: "dsh-github-steps-list" }, h("li", null, "打开系统终端(PowerShell / CMD / Terminal),运行上述命令;"), h("li", null, "按提示依次选择「GitHub.com」➜「HTTPS」➜「Login with a web browser」;"), h("li", null, "按回车打开浏览器确认授权后,切回本页面即可自动恢复。"))
|
|
870
|
+
: null,
|
|
871
|
+
// 操作按钮区域
|
|
872
|
+
h("div", { className: "dsh-github-guide-actions" }, h("button", {
|
|
873
|
+
className: "dsh-github-action-btn",
|
|
874
|
+
onClick: () => launchTerminal(defaultCmd),
|
|
875
|
+
disabled: launching,
|
|
876
|
+
title: "自动唤起系统终端并执行该命令"
|
|
877
|
+
}, launching ? "正在唤起..." : (notInstalled ? "💻 唤起终端安装" : "💻 唤起终端登录")), notInstalled
|
|
878
|
+
? h("button", {
|
|
879
|
+
className: "dsh-github-action-btn",
|
|
880
|
+
onClick: openDownload,
|
|
881
|
+
title: "打开 GitHub CLI 官方下载主页获取安装包"
|
|
882
|
+
}, "🌐 官网安装包 (.msi/.pkg)")
|
|
883
|
+
: null, h("button", {
|
|
884
|
+
className: "dsh-github-action-btn",
|
|
885
|
+
onClick: onRefresh,
|
|
886
|
+
title: "重新检测登录与安装状态"
|
|
887
|
+
}, "🔄 刷新状态")), toastMsg
|
|
888
|
+
? h("div", { style: { fontSize: "12px", color: "var(--dsw-alias-link, #0969da)", marginTop: "8px" } }, `ℹ️ ${toastMsg}`)
|
|
889
|
+
: null, h("div", { className: "dsh-github-guide-tip" }, notInstalled
|
|
890
|
+
? "💡 提示:使用包管理器安装完成后,若未立即识别,可重启 DeepSeek Harness 应用刷新系统环境变量 PATH。"
|
|
891
|
+
: "💡 提示:凭证由官方 GitHub CLI 加密保存在操作系统安全凭据库中,授权完毕切回本窗口将自动识别刷新。"));
|
|
892
|
+
}
|
|
675
893
|
// 渲染 GitHub 仓库信息与操作视图的核心函数
|
|
676
894
|
function renderGithubRepoView(h, data, loading, error, onRefresh, isRightbar = false) {
|
|
677
895
|
var auth = data ? data.auth : null;
|
|
@@ -749,7 +967,10 @@ body[data-ds-dark-theme] .dsh-github-subheading {
|
|
|
749
967
|
: r.conclusion === "failure"
|
|
750
968
|
? h("span", { className: "dsh-github-badge dsh-badge-red" }, "✕ 失败")
|
|
751
969
|
: h("span", { className: "dsh-github-badge dsh-badge-yellow" }, r.status))))));
|
|
752
|
-
|
|
970
|
+
var authGuideEl = !auth?.loggedIn
|
|
971
|
+
? h(AuthGuideCard, { auth, onRefresh, isRightbar })
|
|
972
|
+
: null;
|
|
973
|
+
return [headerEl, authGuideEl, repoCardEl, actionsBarEl, prSectionEl, runSectionEl].filter(Boolean);
|
|
753
974
|
}
|
|
754
975
|
// 1. 右侧栏专属视图 (Right Sidebar Tab Body)
|
|
755
976
|
function GithubRightbarBody(props) {
|
|
@@ -806,6 +1027,16 @@ body[data-ds-dark-theme] .dsh-github-subheading {
|
|
|
806
1027
|
React.useEffect(() => {
|
|
807
1028
|
loadData(false);
|
|
808
1029
|
}, [loadData]);
|
|
1030
|
+
// 窗口切回自动探测自愈
|
|
1031
|
+
React.useEffect(() => {
|
|
1032
|
+
var onFocus = () => {
|
|
1033
|
+
if (!data?.auth?.loggedIn) {
|
|
1034
|
+
loadData(false);
|
|
1035
|
+
}
|
|
1036
|
+
};
|
|
1037
|
+
window.addEventListener("focus", onFocus);
|
|
1038
|
+
return () => window.removeEventListener("focus", onFocus);
|
|
1039
|
+
}, [data, loadData]);
|
|
809
1040
|
var h = React.createElement;
|
|
810
1041
|
return h("div", { className: "dsh-github-rightbar-container" }, ...renderGithubRepoView(h, data, loading, error, () => loadData(true), true));
|
|
811
1042
|
}
|
|
@@ -837,29 +1068,57 @@ body[data-ds-dark-theme] .dsh-github-subheading {
|
|
|
837
1068
|
var loadData = React.useCallback(async (forceRefresh = false) => {
|
|
838
1069
|
setLoading(true);
|
|
839
1070
|
setError(null);
|
|
1071
|
+
// 全局驾驶舱要 spawn 十几次 gh,冷启动可能超过 10 秒。
|
|
1072
|
+
// 不设显式超时的话浏览器会抛一个与真实原因无关的网络错误,
|
|
1073
|
+
// 早先这里会把所有失败都归因成「后端服务未就绪或接口不存在」——归因错误比没有报错更糟。
|
|
1074
|
+
var controller = new AbortController();
|
|
1075
|
+
var timedOut = false;
|
|
1076
|
+
var timer = setTimeout(function () { timedOut = true; controller.abort(); }, 30000);
|
|
840
1077
|
try {
|
|
841
1078
|
if (forceRefresh)
|
|
842
1079
|
await fetch("/api/github/refresh", { method: "POST" }).catch(() => { });
|
|
843
|
-
var res = await fetch("/api/github/global-overview");
|
|
1080
|
+
var res = await fetch("/api/github/global-overview", { signal: controller.signal });
|
|
844
1081
|
if (!res.ok) {
|
|
845
1082
|
var errText = await res.text().catch(() => "");
|
|
846
|
-
setError(
|
|
1083
|
+
setError(`获取全局数据失败(HTTP ${res.status})${errText ? ": " + errText : ""}`);
|
|
847
1084
|
return;
|
|
848
1085
|
}
|
|
849
1086
|
var json = await res.json();
|
|
850
|
-
if (json.ok && json.data)
|
|
1087
|
+
if (json.ok && json.data) {
|
|
851
1088
|
setData(json.data);
|
|
852
|
-
|
|
1089
|
+
// 工作区矩阵单独失败时不能静默:否则界面显示「暂无工作区」,把故障伪装成空状态
|
|
1090
|
+
if (json.data.workspaceError) {
|
|
1091
|
+
setError("工作区列表读取失败:" + json.data.workspaceError);
|
|
1092
|
+
}
|
|
1093
|
+
}
|
|
1094
|
+
else {
|
|
853
1095
|
setError(json.error || "获取全局数据失败");
|
|
1096
|
+
}
|
|
854
1097
|
}
|
|
855
1098
|
catch (err) {
|
|
856
|
-
|
|
1099
|
+
if (timedOut || err.name === "AbortError") {
|
|
1100
|
+
setError("获取超时(30 秒):本机 gh 命令响应过慢,通常是网络或工作区数量较多所致。可稍后重试,或减少同时打开的工作区。");
|
|
1101
|
+
}
|
|
1102
|
+
else {
|
|
1103
|
+
setError("无法连接后端服务:" + (err.message || err));
|
|
1104
|
+
}
|
|
857
1105
|
}
|
|
858
1106
|
finally {
|
|
1107
|
+
clearTimeout(timer);
|
|
859
1108
|
setLoading(false);
|
|
860
1109
|
}
|
|
861
1110
|
}, []);
|
|
862
1111
|
React.useEffect(() => { loadData(false); }, [loadData]);
|
|
1112
|
+
// 窗口切回自动探测自愈
|
|
1113
|
+
React.useEffect(() => {
|
|
1114
|
+
var onFocus = () => {
|
|
1115
|
+
if (!data?.auth?.loggedIn) {
|
|
1116
|
+
loadData(false);
|
|
1117
|
+
}
|
|
1118
|
+
};
|
|
1119
|
+
window.addEventListener("focus", onFocus);
|
|
1120
|
+
return () => window.removeEventListener("focus", onFocus);
|
|
1121
|
+
}, [data, loadData]);
|
|
863
1122
|
var h = React.createElement;
|
|
864
1123
|
var headerEl = h("div", { className: "dsh-github-header" }, h("div", { className: "dsh-github-title" }, renderOctocatBadge(32), h("h2", null, "GitHub 全景驾驶舱")), h("button", {
|
|
865
1124
|
className: "dsh-github-refresh-btn",
|
|
@@ -876,7 +1135,19 @@ body[data-ds-dark-theme] .dsh-github-subheading {
|
|
|
876
1135
|
var myIssues = (data && data.myIssues) || [];
|
|
877
1136
|
var errorEl = error ? h("div", { style: { color: "#f85149", background: "rgba(248,81,73,0.1)", border: "1px solid rgba(248,81,73,0.3)", padding: "8px 12px", borderRadius: "6px", marginBottom: "16px", fontSize: "12px" } }, `⚠️ 加载失败: ${error}`) : null;
|
|
878
1137
|
// 顶部统计卡片
|
|
879
|
-
var statsCardsEl = h("div", { className: "dsh-github-overview-cards" }, h("div", { className: "dsh-github-card" }, h("div", { className: "dsh-github-card-title" }, "CLI 认证账号"), h("div", { className: "dsh-github-card-value" }, auth?.loggedIn
|
|
1138
|
+
var statsCardsEl = h("div", { className: "dsh-github-overview-cards" }, h("div", { className: "dsh-github-card" }, h("div", { className: "dsh-github-card-title" }, "CLI 认证账号"), h("div", { className: "dsh-github-card-value" }, auth?.loggedIn
|
|
1139
|
+
? `👤 ${auth.user}`
|
|
1140
|
+
: !auth?.installed
|
|
1141
|
+
? "❌ 未安装 CLI"
|
|
1142
|
+
: "⚠️ 未登录", auth?.loggedIn
|
|
1143
|
+
? h("span", { className: "dsh-github-badge dsh-badge-green" }, "Active")
|
|
1144
|
+
: !auth?.installed
|
|
1145
|
+
? h("span", { className: "dsh-github-badge dsh-badge-red" }, "Missing")
|
|
1146
|
+
: h("span", { className: "dsh-github-badge dsh-badge-yellow" }, "Required"))), h("div", { className: "dsh-github-card" }, h("div", { className: "dsh-github-card-title" }, "账号云端仓库"), h("div", { className: "dsh-github-card-value" }, `${userRepos.length} 个 Repositories`)), h("div", { className: "dsh-github-card" }, h("div", { className: "dsh-github-card-title" }, "本地工作区联动"), h("div", { className: "dsh-github-card-value" }, `${workspaceMatrix.length} 个工作区 (${workspaceMatrix.filter((w) => w.repo).length} 个已关联)`)));
|
|
1147
|
+
// 安装与登录引导组件(未登录或未安装时显式展开)
|
|
1148
|
+
var authGuideEl = !auth?.loggedIn
|
|
1149
|
+
? h(AuthGuideCard, { auth, onRefresh: () => loadData(true), isRightbar: false })
|
|
1150
|
+
: null;
|
|
880
1151
|
// Tab 栏
|
|
881
1152
|
var tabsEl = h("div", { className: "dsh-github-tabs" }, h("button", { className: `dsh-github-tab-btn ${activeTab === 'workspaces' ? 'active' : ''}`, onClick: () => setActiveTab('workspaces') }, `📂 本地工作区矩阵 (${workspaceMatrix.length})`), h("button", { className: `dsh-github-tab-btn ${activeTab === 'repos' ? 'active' : ''}`, onClick: () => setActiveTab('repos') }, `☁️ 我的 GitHub 仓库 (${userRepos.length})`), h("button", { className: `dsh-github-tab-btn ${activeTab === 'mywork' ? 'active' : ''}`, onClick: () => setActiveTab('mywork') }, `📋 个人待办 (PR: ${myPrs.length} · Issue: ${myIssues.length})`));
|
|
882
1153
|
// 内容区
|
|
@@ -889,18 +1160,28 @@ body[data-ds-dark-theme] .dsh-github-subheading {
|
|
|
889
1160
|
: null))));
|
|
890
1161
|
}
|
|
891
1162
|
else if (activeTab === 'repos') {
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
1163
|
+
if (!auth?.loggedIn) {
|
|
1164
|
+
contentEl = h("div", { className: "dsh-github-empty" }, "当前尚未认证登录 GitHub 账号,无法读取云端仓库列表。请先完成上方的安装或登录指引。");
|
|
1165
|
+
}
|
|
1166
|
+
else {
|
|
1167
|
+
var filteredRepos = userRepos.filter((r) => !searchQuery || r.nameWithOwner.toLowerCase().includes(searchQuery.toLowerCase()) || r.description?.toLowerCase().includes(searchQuery.toLowerCase()));
|
|
1168
|
+
contentEl = h("div", null, h("input", {
|
|
1169
|
+
className: "dsh-github-search-input",
|
|
1170
|
+
placeholder: "🔍 搜索我的 GitHub 仓库...",
|
|
1171
|
+
value: searchQuery,
|
|
1172
|
+
onChange: (e) => setSearchQuery(e.target.value)
|
|
1173
|
+
}), h("div", { className: "dsh-github-list" }, filteredRepos.map((r) => h("div", { key: r.nameWithOwner, className: "dsh-github-item" }, h("a", { className: "dsh-github-item-title", href: r.url, target: "_blank" }, r.nameWithOwner), r.description ? h("div", { className: "dsh-github-item-desc" }, r.description) : null, h("div", { className: "dsh-github-item-meta" }, h("span", null, `默认分支: ${r.defaultBranch}`), r.isPrivate ? h("span", { className: "dsh-github-badge dsh-badge-yellow" }, "Private") : h("span", { className: "dsh-github-badge dsh-badge-green" }, "Public"), h("span", null, `⭐ ${r.stargazerCount}`))))));
|
|
1174
|
+
}
|
|
899
1175
|
}
|
|
900
1176
|
else {
|
|
901
|
-
|
|
1177
|
+
if (!auth?.loggedIn) {
|
|
1178
|
+
contentEl = h("div", { className: "dsh-github-empty" }, "当前尚未认证登录 GitHub 账号,无法读取个人待办 PR 与 Issues。请先完成上方的安装或登录指引。");
|
|
1179
|
+
}
|
|
1180
|
+
else {
|
|
1181
|
+
contentEl = h("div", null, h("h3", { className: "dsh-github-subheading" }, `🔀 我发起的 PR (${myPrs.length})`), myPrs.length === 0 ? h("div", { className: "dsh-github-empty", style: { marginBottom: "16px" } }, "暂无开放中的 PR") : h("div", { className: "dsh-github-list", style: { marginBottom: "16px" } }, myPrs.map((pr) => h("div", { key: pr.number, className: "dsh-github-item" }, h("a", { className: "dsh-github-item-title", href: pr.url, target: "_blank" }, `[${pr.repository?.nameWithOwner || 'Repo'}] #${pr.number} ${pr.title}`)))), h("h3", { className: "dsh-github-subheading" }, `📝 我的待办 Issues (${myIssues.length})`), myIssues.length === 0 ? h("div", { className: "dsh-github-empty" }, "暂无开放中的 Issue") : h("div", { className: "dsh-github-list" }, myIssues.map((is) => h("div", { key: is.number, className: "dsh-github-item" }, h("a", { className: "dsh-github-item-title", href: is.url, target: "_blank" }, `[${is.repository?.nameWithOwner || 'Repo'}] #${is.number} ${is.title}`)))));
|
|
1182
|
+
}
|
|
902
1183
|
}
|
|
903
|
-
return h("div", { className: "dsh-github-panel" }, headerEl, errorEl, statsCardsEl, tabsEl, contentEl);
|
|
1184
|
+
return h("div", { className: "dsh-github-panel" }, headerEl, errorEl, statsCardsEl, authGuideEl, tabsEl, contentEl);
|
|
904
1185
|
}
|
|
905
1186
|
var inject = ["slots", "sidebarRightTabs", "sessions", "layout", "theme"];
|
|
906
1187
|
function apply(ctx) {
|
package/lib/commands/gh.js
CHANGED
|
@@ -7,7 +7,9 @@ export function registerGhCommand(commandsService, executor) {
|
|
|
7
7
|
async handler({ rawInput }) {
|
|
8
8
|
const trimmed = (rawInput || '').trim();
|
|
9
9
|
if (!trimmed || trimmed === 'status') {
|
|
10
|
-
|
|
10
|
+
// 人类显式诊断必须绕过缓存:用户敲 /gh status 往往正是为了确认
|
|
11
|
+
// 「我刚 gh auth login 到底成没成」,给他 30 秒前的缓存等于答非所问。
|
|
12
|
+
const auth = await executor.checkAuth(undefined, true);
|
|
11
13
|
const repo = await executor.getRepoMetadata();
|
|
12
14
|
let md = `### GitHub 集成状态\n\n`;
|
|
13
15
|
if (auth.loggedIn) {
|
package/lib/executor.d.ts
CHANGED
|
@@ -1,8 +1,21 @@
|
|
|
1
1
|
import type { GhExecutionOptions, GhResult, AuthStatus, RepoMetadata, PluginConfig } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* 按「字符数 + UTF-8 字节数」双口径截断输出。
|
|
4
|
+
* 抽成纯函数是刻意的:截断逻辑是崩溃高发区(切碎多字节字符、误伤 JSON、把二进制流截成垃圾),
|
|
5
|
+
* 但通过真实 gh 命令很难稳定复现超限输出,抽出来才能用单元测试覆盖边界。
|
|
6
|
+
*/
|
|
7
|
+
export declare function truncateOutput(input: string, maxChars: number, maxBytes: number): {
|
|
8
|
+
text: string;
|
|
9
|
+
truncated: boolean;
|
|
10
|
+
};
|
|
2
11
|
export declare class GhExecutor {
|
|
3
12
|
ghPath: string;
|
|
4
13
|
defaultTimeoutMs: number;
|
|
5
14
|
maxOutputChars: number;
|
|
15
|
+
/** UTF-8 字节口径的硬上限;未配置时按 maxOutputChars × 4 推导(覆盖 CJK 3 字节与 emoji 4 字节) */
|
|
16
|
+
maxOutputBytes: number;
|
|
17
|
+
/** 认证状态缓存。`gh auth status` 实测 5.4 秒,而它位于全局驾驶舱关键路径的最前面 */
|
|
18
|
+
private authCache;
|
|
6
19
|
constructor(config?: PluginConfig);
|
|
7
20
|
updateConfig(config?: PluginConfig): void;
|
|
8
21
|
/**
|
|
@@ -20,8 +33,26 @@ export declare class GhExecutor {
|
|
|
20
33
|
run<T = unknown>(args: string[], options?: GhExecutionOptions): Promise<GhResult<T>>;
|
|
21
34
|
/**
|
|
22
35
|
* 检查宿主机当前 GitHub CLI 的认证状态与账号信息
|
|
36
|
+
*
|
|
37
|
+
* @param cwd 解析工作区用的路径
|
|
38
|
+
* @param bypassCache 跳过进程内缓存。`/gh status` 这类人类显式发起的诊断必须看到实时结果,
|
|
39
|
+
* 而 Web 面板每次挂载都会来问一次,重复执行实测要 5.4 秒,属于纯浪费。
|
|
23
40
|
*/
|
|
24
|
-
checkAuth(cwd?: string): Promise<AuthStatus>;
|
|
41
|
+
checkAuth(cwd?: string, bypassCache?: boolean): Promise<AuthStatus>;
|
|
42
|
+
/** 生成当前操作系统平台的推荐安装引导与命令 */
|
|
43
|
+
private getPlatformInstallGuide;
|
|
44
|
+
/** 实际执行 `gh auth status` 并解析结果,不含缓存逻辑 */
|
|
45
|
+
private probeAuth;
|
|
46
|
+
/**
|
|
47
|
+
* 仅从本地 .git/config 解析仓库身份,绝不发起网络请求。
|
|
48
|
+
*
|
|
49
|
+
* 为什么需要它:全局驾驶舱要为每个工作区认识别仓库,而 getRepoMetadata 会调用
|
|
50
|
+
* `gh repo view` 走网络。实测 5 个工作区时该端点冷启动 20.5 秒,其中绝大部分耗在这 5 次
|
|
51
|
+
* 网络往返上——而有界并发根本救不了它,因为瓶颈是网络延迟而不是进程排队。
|
|
52
|
+
* 工作区矩阵只需要「这个目录属于哪个仓库」这一身份信息,本地 .git/config 已经完整具备,
|
|
53
|
+
* 描述/star 数等富字段是当前仓库详情才需要的。这一步把每个工作区从 1 次网络往返降为 0。
|
|
54
|
+
*/
|
|
55
|
+
getRepoIdentityFromLocal(cwd?: string): RepoMetadata | null;
|
|
25
56
|
/**
|
|
26
57
|
* 获取当前目录(工作区)关联的远程仓库元数据
|
|
27
58
|
* 采用 gh repo view + 本地 git remote 双保险机制,彻底杜绝已配置远程却误判未关联的问题
|