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.
- package/README.en.md +68 -22
- package/README.md +69 -23
- package/docs/UPGRADE-NOTES.md +93 -0
- package/docs/architecture.md +76 -31
- package/docs/images/1.png +0 -0
- package/docs/images/2.png +0 -0
- package/docs/images/3.png +0 -0
- package/docs/images/4.png +0 -0
- package/docs/npm-publish-guide.md +27 -5
- package/lib/api/routes-actions.d.ts +18 -4
- package/lib/api/routes-actions.js +143 -19
- package/lib/api/routes-lifecycle.d.ts +1 -1
- package/lib/api/routes-lifecycle.js +53 -2
- package/lib/api/routes-overview.js +104 -1
- package/lib/client.js +991 -55
- package/lib/commands/gh.js +11 -2
- package/lib/executor.d.ts +118 -1
- package/lib/executor.js +479 -12
- package/lib/main.js +10 -0
- package/lib/renderers/markdown.d.ts +12 -1
- package/lib/renderers/markdown.js +150 -16
- package/lib/tools/help.js +144 -11
- package/lib/tools/image-utils.d.ts +79 -0
- package/lib/tools/image-utils.js +247 -0
- package/lib/tools/issue.d.ts +18 -4
- package/lib/tools/issue.js +161 -6
- package/lib/tools/pr.d.ts +12 -4
- package/lib/tools/pr.js +128 -7
- package/lib/tools/repo.d.ts +20 -4
- package/lib/tools/repo.js +196 -4
- 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/types.d.ts +85 -1
- package/lib/update-settings.d.ts +18 -0
- package/lib/update-settings.js +60 -0
- package/lib/updater.d.ts +32 -1
- package/lib/updater.js +117 -9
- package/lib/upgrade-manager.d.ts +70 -1
- package/lib/upgrade-manager.js +346 -46
- package/lib/version.d.ts +1 -1
- package/lib/version.js +1 -1
- package/package.json +1 -1
package/docs/architecture.md
CHANGED
|
@@ -20,24 +20,34 @@
|
|
|
20
20
|
│ │ GitHubService (ctx.github 原生服务) │ │
|
|
21
21
|
│ │ - checkAuth(cwd) - getRepoMetadata(cwd) │ │
|
|
22
22
|
│ │ - run<T>(args, opts) [execFile 数组隔离 + 24KB 智能截断] │ │
|
|
23
|
-
│ │
|
|
24
|
-
│ │ -
|
|
23
|
+
│ │ - runGuarded({minVersion,fallback}) [gh 版本守卫降级链] │ │
|
|
24
|
+
│ │ - getGhVersion() [30s TTL 缓存·只存成功值] │ │
|
|
25
|
+
│ │ - AI_AGENT=dsh 注入 [gh 官方 agent 识别·可配置开关] │ │
|
|
26
|
+
│ │ - UpdateChecker (源可选探测 + 24h 缓存 + Fail-Safe) │ │
|
|
25
27
|
│ │ - UpgradeManager (跨平台自适应 + 互斥锁 + macOS PATH 补全) │ │
|
|
28
|
+
│ │ - spawnWithStdin (密文通道: spawn+stdin.end + kill 超时) │ │
|
|
26
29
|
│ └──────┬───────────────────────┬────────────────────────┬──────┘ │
|
|
27
30
|
│ │ │ │ │
|
|
28
31
|
│ ▼ ▼ ▼ │
|
|
29
32
|
│ ┌──────────────────┐ ┌──────────────────┐ ┌───────────────────────┐ │
|
|
30
33
|
│ │ 9 大 Agent 工具 │ │ 人类 Slash 指令 │ │ Host HTTP WebServer │ │
|
|
31
34
|
│ │ (defineTool DSL)│ │ /gh status │ │ (Effect Disposer 清理)│ │
|
|
32
|
-
│ │ - pr
|
|
33
|
-
│ │
|
|
34
|
-
│ │ -
|
|
35
|
-
│ │
|
|
36
|
-
│ │
|
|
37
|
-
│ │ -
|
|
38
|
-
│ │ -
|
|
39
|
-
│ │ -
|
|
40
|
-
│ │
|
|
35
|
+
│ │ - pr (+ready· │ │ /gh upgrade │ │ - /api/github/overview│ │
|
|
36
|
+
│ │ revert·update)│ │ /gh help │ │ - /api/github/action │ │
|
|
37
|
+
│ │ - issue (+delete│ └──────────────────┘ │ - /api/github/refresh │ │
|
|
38
|
+
│ │ ·transfer·pin·│ │ - /check-update (并行)│ │
|
|
39
|
+
│ │ lock·status) │ │ - /upgrade (一键自动) │ │
|
|
40
|
+
│ │ - run (+workflow│ │ - /update-settings │ │
|
|
41
|
+
│ │ 系列·download)│ (workflow_enable/disable │ │ - /proxy (全局代理) │ │
|
|
42
|
+
│ │ - repo (+search │ 启停·list/view·download) │ - /auth (轻量探针) │ │
|
|
43
|
+
│ │ 三域+read_ │ └──────────┬────────────┘ │
|
|
44
|
+
│ │ file &read_dir)│ │ │
|
|
45
|
+
│ │ - api (逃生门) │ (search 三域=issues/prs/commits; │ │
|
|
46
|
+
│ │ - release │ read_file/read_dir 免clone读取) │ │
|
|
47
|
+
│ │ - secret (stdin │ │ │
|
|
48
|
+
│ │ +variable 域) │ │ │
|
|
49
|
+
│ │ - skill │ │ │
|
|
50
|
+
│ │ - help (元指南) │ │ │
|
|
41
51
|
│ └────────┬─────────┘ │ │
|
|
42
52
|
│ │ │ │
|
|
43
53
|
│ ▼ │ │
|
|
@@ -54,13 +64,15 @@
|
|
|
54
64
|
│ │
|
|
55
65
|
│ ┌──────────────────────────────────┐ ┌─────────────────────────────┐ │
|
|
56
66
|
│ │ 全局驾驶舱 (Main Slot) │ │ 右侧栏面板 (Right Sidebar) │ │
|
|
57
|
-
│ │ - 账号全局所有 GitHub 仓库 │ │ -
|
|
58
|
-
│ │ - 本地工作区矩阵透视 │ │
|
|
59
|
-
│ │ - 三维待办 Issues (药丸筛选) │ │ -
|
|
60
|
-
│ │ - 用户提单 / 分配待办 / 我发起 │ │ -
|
|
61
|
-
│ │ - 顶部升级通知横幅 (Banner) │ │ -
|
|
62
|
-
│ │ - 动态数据新鲜度胶囊与 SWR 缓存│ │ -
|
|
63
|
-
│ │ -
|
|
67
|
+
│ │ - 账号全局所有 GitHub 仓库 │ │ - 五 Tab 互斥 (Git/未提交/ │ │
|
|
68
|
+
│ │ - 本地工作区矩阵透视 │ │ PR/Issues/Actions) │ │
|
|
69
|
+
│ │ - 三维待办 Issues (药丸筛选) │ │ - Git 记录与分支同步全景 │ │
|
|
70
|
+
│ │ - 用户提单 / 分配待办 / 我发起 │ │ - 提交详情居中弹窗 (可滑动)│ │
|
|
71
|
+
│ │ - 顶部升级通知横幅 (Banner) │ │ - 下滑自动分页加载更多 │ │
|
|
72
|
+
│ │ - 动态数据新鲜度胶囊与 SWR 缓存│ │ - 快捷操作网格与设置入口 │ │
|
|
73
|
+
│ │ - 四分区设置弹窗 (同步/代理/ │ │ - 官方标准 Git 矢量图标 │ │
|
|
74
|
+
│ │ 更新源/关于) │ │ - 「插件」身份徽章 (直达) │ │
|
|
75
|
+
│ │ - 「插件」身份徽章 (关于直达) │ │ │ │
|
|
64
76
|
│ └──────────────────────────────────┘ └─────────────────────────────┘ │
|
|
65
77
|
└─────────────────────────────────────────────────────────────────────────────┘
|
|
66
78
|
```
|
|
@@ -83,11 +95,19 @@
|
|
|
83
95
|
- 所有的 `gh` 查询原生采用 `--json <fields>` 结构化投影;
|
|
84
96
|
- 对长文本输出(如大型 PR 的完整 `diff`、CI 失败步骤日志)设置安全截断阈值(默认 24KB),采用码点安全切割与二分查找字节截断,保留头尾关键上下文并添加显式截断标记,杜绝撑爆模型上下文;
|
|
85
97
|
- 列表与详情输出经独立 Markdown 渲染引擎(`src/renderers/markdown.ts`)转化为高密度表格与卡片(带状态徽章、可点击超链接与竖线转义防护),实测 20 条 PR 列表输出压缩 70%+ Token;
|
|
86
|
-
- 所有 Agent 工具的输出统一经过归一化,列表自动打包为 `{ items, count }`,标量打包为 `{ result }`,100% 严丝合缝契合 DSH
|
|
98
|
+
- 所有 Agent 工具的输出统一经过归一化,列表自动打包为 `{ items, count }`,标量打包为 `{ result }`,100% 严丝合缝契合 DSH 工具网关模式约束;
|
|
99
|
+
- **gh 版本守卫降级链(runGuarded)**:preview 命令与新特性按 `minVersion` 门槛路由——版本达标直通主命令、不足走 `fallback` 降级链路(结果携带 `degraded/degradedReason` 结构化标注,**降级不是静默的**,渲染层对 degraded 结果显式追加链路说明)、无降级时报错附平台升级指引;版本探测自身失败 fail-open 直通(守卫是路由优化不是安全边界)。版本比较采用数值分段(杜绝 `'2.96.0' > '2.100.0'` 字典序陷阱),探测结果 30s TTL 缓存且只缓存成功值;
|
|
100
|
+
- **AI_AGENT 识别信号注入**:每次 gh 子进程启动注入 `AI_AGENT=dsh`(gh detect.go 最高优先级通用信号)——gh ≥2.99 自动切换 text-only spinner、≥2.100 命令误用时打印完整 help(模型可据此自我纠正)、telemetry 归因到 dsh;旧版 gh 忽略该变量零副作用。经 `agentEnvInjection` 配置开关(默认开启)可恢复 gh 原生行为。
|
|
87
101
|
|
|
88
102
|
### 2.3.1 高危操作确认与敏感值安全通道 (Safety Confirmation & Secret stdin Channel)
|
|
89
|
-
- 不可逆写操作(`merge` / `close` / `release delete` / clobber 覆盖上传 / `secret delete` / `skill install`)强制要求 `confirm: true` 显式参数,未确认时仅返回影响评估卡片,不产生任何远端或本地副作用;
|
|
90
|
-
- 密钥明文仅经子进程标准输入传递(`execFile` 的 `input`
|
|
103
|
+
- 不可逆写操作(`merge` / `close` / `issue delete`(物理删除)/ `issue transfer`(跨仓库迁移)/ `pr revert`(远端创建反向 PR)/ `release delete` / clobber 覆盖上传 / `secret delete` / `skill install`)强制要求 `confirm: true` 显式参数,未确认时仅返回影响评估卡片,不产生任何远端或本地副作用;
|
|
104
|
+
- 密钥明文仅经子进程标准输入传递(`spawnWithStdin` 通道:`spawn` + `stdin.end(data)` 半关闭写入——`execFile` 的 `input` 选项在实测环境从不真正写入管道,密文通道此前从未生效过),全程不出现在命令行参数、进程列表与工具输出中;渲染层对 secret 输出采用独立实现,从结构上隔离「绝不渲染 value 字段」这条安全红线。secret 与 variable(CI 明文,get 可回显)的渲染走**两个物理独立的函数**——安全语义靠函数边界而非同一函数的分支隔离,防未来重构互相污染。
|
|
105
|
+
|
|
106
|
+
### 2.3.2 产物落盘纪律 (Artifact Download Discipline)
|
|
107
|
+
- **目录固定派生,永不暴露路径参数**:构建产物下载(`run download` 等)的落盘目录由插件从 `~/.dsh/data/dsh-github-flow/artifacts/<owner>/<repo>/<run_id>/` 三级固定派生,不向调用方暴露任何目录参数——这是 GHSA-39wj-f2f4-978v(gh <2.102 符号链接路径穿越)的核心防线:用户无法注入 `../` 或符号链接目标,攻击面收敛到唯一一个 `mkdirSync(recursive)` 调用点,且目录天然按仓库与 run 隔离、可整体清理;
|
|
108
|
+
- **低版本安全提示而非硬拒绝**:gh <2.102 时下载仍可执行(受控仓库场景是合理需求),但返回值携带 `securityNote` 升级指引;需要自定义落盘位置的场景显式引导经 `github_api` 手动拉取;
|
|
109
|
+
- **下载后零本地符号化操作**:插件对落盘产物仅做递归枚举(相对路径 + 大小清单),不执行链接、移动或执行类操作。
|
|
110
|
+
未来任何新增下载类能力(如 gist 资产)必须引用本节纪律,而非各自重述。
|
|
91
111
|
|
|
92
112
|
### 2.4 原生浅色/深色主题自适应与快捷操作网格
|
|
93
113
|
- 全面对齐 DSH 官方 DSW 语义设计变量,基于 `body[data-ds-dark-theme]` 驱动无闪烁双模式切换;
|
|
@@ -114,12 +134,15 @@
|
|
|
114
134
|
- **双端视图无缝打标**:驾驶舱提供四态响应式药丸筛选(全部/用户提单/指派待办/我发起的),右侧栏直接常驻展现当前工作区所属 Issue 任务清单与状态徽章。
|
|
115
135
|
|
|
116
136
|
### 2.8 智能版本检测与跨平台一键升级引擎 (Update & Upgrade Engine)
|
|
117
|
-
-
|
|
118
|
-
-
|
|
119
|
-
-
|
|
120
|
-
-
|
|
121
|
-
-
|
|
122
|
-
-
|
|
137
|
+
- **检查与 gh 链路完全并行**:
|
|
138
|
+
- 前端版本检查与 gh 环境探测(安装/登录/业务数据)**零依赖并行发起**(挂载即触发,仅 300ms 微延迟避让首屏),不串行等待概览返回,gh 环境异常时升级横幅照常展示;
|
|
139
|
+
- 服务端 `inFlightPromise` 请求合流 + 24 小时缓存,多面板同时挂载零请求风暴。
|
|
140
|
+
- **更新源三态可选(UI 持久化 store 优先)**:
|
|
141
|
+
- 三态单选:官方源(默认)/ 国内镜像 npmmirror / 关闭自动检查;
|
|
142
|
+
- 持久化于 `~/.dsh/data/dsh-github-flow/update-settings.json`,保存即热切换并失效 24h 缓存(防"切换无效"感知);
|
|
143
|
+
- **单源严格不互备**:显式选择的源不静默回落——镜像同步滞后正是选官方的理由,探测失败如实返回;
|
|
144
|
+
- 「关闭检查」语义为关自动提示不关能力:`/gh upgrade`(force)与一键升级路由不受熔断;
|
|
145
|
+
- 一键升级的 `NPM_CONFIG_REGISTRY` 跟随所选源(注入查询函数而非静态值,切源实时生效)。
|
|
123
146
|
- **跨平台 Shell 调度与 macOS PATH 自愈**:
|
|
124
147
|
- **Windows**:采用 `cmd.exe /d /s /c`,彻底规避 PowerShell 5.1 不支持 `||` 导致的语法崩溃,启动仅 ~10ms;
|
|
125
148
|
- **macOS / Linux**:采用用户 Default Shell(`/bin/zsh` 或 `/bin/bash`);
|
|
@@ -138,10 +161,32 @@
|
|
|
138
161
|
- **透明数据新鲜度与状态胶囊 (SyncStatusBadge)**:
|
|
139
162
|
- 胶囊状态精准映射:`[⏳ 后台同步中...]`、`[✅ 已是最新 (15:30)]`、`[📦 本地快照 (X分钟前)]`、`[⚠️ 同步超时已保留缓存]`;
|
|
140
163
|
- 支持人性化相对时间推导(刚刚、X分钟前、X小时前、X天前),消除用户对数据时效性的疑虑。
|
|
141
|
-
-
|
|
142
|
-
-
|
|
143
|
-
-
|
|
144
|
-
-
|
|
164
|
+
- **设置中心 (GlobalSyncSettingsModal) 四分区**:
|
|
165
|
+
- **🔄 数据同步**:`⚡ 秒开模式 (SWR, 默认)` / `📋 仅手动刷新 (Manual Only)` 两态选择;
|
|
166
|
+
- **🌐 网络代理**:代理配置即时全局生效(写 proxy.json + 重应用 dispatcher,零插件重载);
|
|
167
|
+
- **⬆️ 更新设置**:更新源三态选择(官方/镜像/关闭),保存即热切换并真实验证;
|
|
168
|
+
- **ℹ️ 关于**:插件身份信息(版本/仓库/npm/协议/求 Star);
|
|
169
|
+
- 弹窗支持 `initialSection` 直开指定分区——插件徽标点击直开「关于」,齿轮默认「数据同步」。
|
|
170
|
+
- **插件身份标识闭环**:
|
|
171
|
+
- 面板标题(驾驶舱/右侧栏)带「插件」徽标,明确标注界面由第三方插件 `dsh-github-flow` 提供(非 DSH / GitHub 官方);
|
|
172
|
+
- 徽标点击派发全局事件打开「关于」弹窗(不直接跳浏览器);多宿主同挂载时经可见性守卫防双层弹窗叠层;
|
|
173
|
+
- 右侧栏同样提供 ⚙️ 设置按钮,与主页复用同一弹窗组件,保存经事件总线双侧实时对齐。
|
|
145
174
|
- **离线极端容灾保护 (Fail-Safe Offline Guard)**:
|
|
146
175
|
- 后台静默重验若遭遇断网、弱网或进程超时,界面平滑降级,**100% 保持旧数据完整可用**,绝不回退至空白占位或报错中断。
|
|
176
|
+
- **数据链路超时防线**:
|
|
177
|
+
- 全部前端数据链路 fetch 绑定 `AbortController`(45s 兜底;登录探测 10s)——代理劫持回环地址等极端场景下用户得到明确超时出口,绝不永久转圈(未安装 gh 用户的刷新卡死修复);
|
|
178
|
+
- 后端 `gh` 探测毫秒级返回 ENOENT/安装引导,前端安装引导卡(AuthGuideCard)承载唤起终端(stdio ignore,无 profile)等自愈出口。
|
|
179
|
+
|
|
180
|
+
### 2.10 右侧栏五 Tab 互斥与本地 Git 状态全景架构 (Right Sidebar Tabs & Local Git Engine)
|
|
181
|
+
- **五 Tab 响应式互斥布局**:
|
|
182
|
+
- 将右侧栏原本纵向堆叠的 PR / Issues / Actions / Git 状态与未提交清单彻底重构成水平紧凑 Tab(Git / 未提交 / PR / Issues / Actions),默认激活 Git 记录页,首屏高度收敛 60% 以上;
|
|
183
|
+
- 纯函数渲染约束与 DOM 级状态:`renderGithubRepoView` 坚守零 React Hooks 约束,激活态通过 `data-active-tab` 挂载在 DOM 容器上;根组件通过 `useEffect([data])` 与 `applyTabState` 联动,在 SWR 后台拉新完成触发重渲染时,自动重置回用户选择的 Tab,彻底解决后台数据刷新拽回默认 Tab 的缺陷。
|
|
184
|
+
- **三段式提交日志与本地 0ms 秒开**:
|
|
185
|
+
- 本地领先(待 push)、远程领先(待 pull,快照口径)与最近提交历史;
|
|
186
|
+
- 采用 ASCII 单元/记录控制符(`%x1e` / `%x1f`)结构化解析 `git log`,一次性提取 fullSha、author、email、date、多行 body 说明与 `--stat` 文件改动统计,首屏 0ms 纯本地呈现,零网络失败风险;
|
|
187
|
+
- 默认展示 15 条,通过右侧栏容器 `onScroll` 触底(<80px)与点击双重触发向后分页拉取,伴随实时进度计数(如 `15 / 74`)。
|
|
188
|
+
- **内嵌模态弹窗 (CommitDetailModal)**:
|
|
189
|
+
- 复用设置弹窗架构(`.dsh-github-modal-overlay` + `.dsh-github-modal-dialog`),挂载在右侧栏容器内部,天然受限于右侧栏边界居中,彻底消除传统 `<dialog>` 顶层穿透全屏居中的越界问题;
|
|
190
|
+
- 弹窗外框限制 `max-height: 84vh; overflow: hidden;`,正文区域 `flex: 1; min-height: 0; overflow-y: auto;`,完美支持上下滑动浏览数十行详细正文与修改文件统计,支持 Esc 键与点击遮罩随手退出。
|
|
191
|
+
- **全库统一 Git 官方矢量图标**:全量替换各处的植物/字符符号为标准 Git 菱形分支官方矢量组件(`#1296db` SVG)。
|
|
147
192
|
|
package/docs/images/1.png
CHANGED
|
Binary file
|
package/docs/images/2.png
CHANGED
|
Binary file
|
package/docs/images/3.png
CHANGED
|
Binary file
|
package/docs/images/4.png
CHANGED
|
Binary file
|
|
@@ -45,23 +45,41 @@
|
|
|
45
45
|
|
|
46
46
|
### 1.3 自动发布操作三步走 (发布新版本日常流程)
|
|
47
47
|
|
|
48
|
-
配置好上述 Secret
|
|
48
|
+
配置好上述 Secret 后,日常发布新版本只需在本地终端运行(完整清单见 [UPGRADE-NOTES.md](./UPGRADE-NOTES.md) 发版必改核对表):
|
|
49
49
|
|
|
50
50
|
```bash
|
|
51
|
-
#
|
|
51
|
+
# 第一步:递增版本号——两处必须同步改(package.json + src/version.ts)!
|
|
52
|
+
# ⚠️ npm version patch 只改 package.json,src/version.ts 需手动同步,
|
|
53
|
+
# 否则 pnpm run build 的版本一致性硬校验会直接失败(v0.2.0 假横幅事故的防线)
|
|
52
54
|
npm version patch # 修补 Bug: 0.1.0 -> 0.1.1
|
|
53
55
|
# 或者 npm version minor # 新增特性: 0.1.0 -> 0.2.0
|
|
56
|
+
# (随后编辑 src/version.ts 的 CURRENT_PLUGIN_VERSION 为同值)
|
|
54
57
|
|
|
55
|
-
#
|
|
58
|
+
# 第二步:构建 + 全量测试(版本校验在此拦截漏改)
|
|
59
|
+
pnpm run build
|
|
60
|
+
pnpm test
|
|
61
|
+
|
|
62
|
+
# 第三步:将代码与标签推送到 GitHub(tag 值必须 = package.json 版本)
|
|
56
63
|
git push origin master --tags
|
|
57
64
|
```
|
|
58
65
|
|
|
59
66
|
**推送完成后**:
|
|
60
67
|
- GitHub Actions 会在 5 秒内自动感知到 `v0.1.1` 标签被创建;
|
|
61
68
|
- 启动 Ubuntu runner,全自动执行:
|
|
62
|
-
`检出代码 ➔ 安装 pnpm ➔ 安装依赖 ➔ pnpm run build (编译 + 清理) ➔ 产物完整性校验 ➔ 带 Provenance 签名发布到 npm ➔ 自动创建并发布对应 GitHub Release
|
|
69
|
+
`检出代码 ➔ 安装 pnpm ➔ 安装依赖 ➔ pnpm run build (编译 + 清理) ➔ 产物完整性校验 ➔ pnpm pack 打离线包 ➔ 带 Provenance 签名发布到 npm ➔ 自动创建并发布对应 GitHub Release(含离线安装包资产)`;
|
|
63
70
|
- 约 1 分钟后,npm 官网版本与 GitHub 仓库 Releases 页面同步更新!
|
|
64
71
|
|
|
72
|
+
### Release 离线安装包资产
|
|
73
|
+
|
|
74
|
+
每个 Release 会附带两个资产文件(与 npm 发布包**内容同源**:同一份 lib/ 构建产物,按 package.json `files` 字段打包):
|
|
75
|
+
|
|
76
|
+
| 资产 | 用途 |
|
|
77
|
+
| :--- | :--- |
|
|
78
|
+
| `dsh-github-flow-<版本>.tgz` | **离线/免构建安装包**:下载后执行 `dsh plugin --profile <名称> add ./dsh-github-flow-<版本>.tgz` 直接安装,无需网络与本地构建。适用场景:pnpm ≥ 10 从 Git 安装的构建授权拦截、npm 镜像同步滞后 24h(Q3 场景的零等待替代)、内网/离线环境分发 |
|
|
79
|
+
| `dsh-github-flow-<版本>.tgz.sha256` | 下载完整性校验(`sha256sum -c` / `Get-FileHash` 比对),供应链信任基线 |
|
|
80
|
+
|
|
81
|
+
> 注:Release 页 GitHub 自动生成的 `Source code (zip/tar.gz)` 是**源码快照**(TypeScript 源码,不可直接安装);`.tgz` 资产才是**编译产物包**(lib/ 已构建),两者用途不同。
|
|
82
|
+
|
|
65
83
|
---
|
|
66
84
|
|
|
67
85
|
## 2. 安全机制与 Provenance 产物溯源
|
|
@@ -99,6 +117,10 @@ npm publish --access public
|
|
|
99
117
|
- **原因**:当前打 tag 的代码版本在 `package.json` 中的 `version` 已经被发布过了。
|
|
100
118
|
- **解法**:本地执行 `npm version patch` 递增版本号后再重新打 tag 推送。
|
|
101
119
|
|
|
120
|
+
### Q2.5: 本地构建报「版本号不一致: package.json=..., src/version.ts=...」
|
|
121
|
+
- **原因**:`npm version` 只更新 package.json,`src/version.ts` 的 `CURRENT_PLUGIN_VERSION` 漏改。
|
|
122
|
+
- **解法**:同步修改 `src/version.ts` 为相同版本后重新构建。这是防「假更新横幅」的硬校验(历史事故:v0.2.0 发布时 version.ts 停留 0.1.8,全体用户被假横幅轰炸)。
|
|
123
|
+
|
|
102
124
|
### Q3: 刚发布了新版本(npm 与淘宝镜像均已更新),为什么用户执行 `dsh plugin add` 依然安装了旧版本?
|
|
103
125
|
- **原因**:**触发了 DSH 底层 pnpm 的供应链安全最小发布年龄保护机制(`minimum-release-age`)**。
|
|
104
126
|
1. DSH 内置的包管理器(pnpm)默认启用了防供应链投毒与恶意包抢跑的防御机制,默认配置规则为 `minimum-release-age = 24 * 60`(即 24 小时 / 1 天);
|
|
@@ -107,6 +129,6 @@ npm publish --access public
|
|
|
107
129
|
- **解法**:
|
|
108
130
|
1. **立即尝鲜安装最新版**:在命令末尾**显式指定目标版本号**(pnpm 会判定为开发者显式信任指定版本,将其加入 `minimumReleaseAgeExclude` 白名单并成功安装):
|
|
109
131
|
```powershell
|
|
110
|
-
dsh plugin --profile desktop add dsh-github-flow
|
|
132
|
+
dsh plugin --profile desktop add dsh-github-flow@<最新版本号>
|
|
111
133
|
```
|
|
112
134
|
2. **自然解禁**:新版本在 npm 上的发布时间达到 24 小时后,安全锁定期自动解除,届时所有用户直接运行裸名安装命令均会默认解析安装最新版。
|
|
@@ -1,9 +1,23 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 动作类路由:缓存强制刷新 + 右侧栏快捷操作(create_pr/create_issue/open_browser/open_terminal)。
|
|
3
|
-
* 共同特征:POST 触发、读少写多、无缓存语义,与概览类路由的演进方向不同。
|
|
4
|
-
*/
|
|
5
1
|
import type { GhExecutor } from '../executor.js';
|
|
6
2
|
import { TtlCache } from '../cache.js';
|
|
7
3
|
import type { GitHubOverviewData, GlobalOverviewData } from '../types.js';
|
|
8
4
|
import { type RouteDeps } from './route-helpers.js';
|
|
5
|
+
/**
|
|
6
|
+
* 构造「在新终端窗口运行命令」的平台命令(execFile 数组形态,不经 shell)。
|
|
7
|
+
*
|
|
8
|
+
* Issue #2 修复要点:
|
|
9
|
+
* - Windows:cmd.exe(显式 .exe,规避 GUI 父进程 PATH 解析差异)+ start 的窗口标题
|
|
10
|
+
* 占位 "DSH GitHub"——start 的首个带引号参数会被解析为标题而非程序,占位后
|
|
11
|
+
* powershell.exe 才能被稳定识别为目标程序;
|
|
12
|
+
* - macOS:osascript 驱动 Terminal.app,命令经引号转义拼入 AppleScript;
|
|
13
|
+
* - Linux:发行版终端二进制与参数形态各异(gnome-terminal 用 `--`、konsole 用
|
|
14
|
+
* `-e`、xfce4-terminal 用 `-x`),单一硬编码在主流桌面大概率不存在——由
|
|
15
|
+
* detectLinuxTerminal 按探测结果返回对应形态。
|
|
16
|
+
*/
|
|
17
|
+
export declare function buildTerminalCommand(command: string): {
|
|
18
|
+
bin: string;
|
|
19
|
+
args: string[];
|
|
20
|
+
} | null;
|
|
21
|
+
/** 测试辅助:重置 Linux 终端探测缓存(便于单测注入不同探测环境) */
|
|
22
|
+
export declare function resetLinuxTerminalCache(): void;
|
|
9
23
|
export declare function registerActionRoutes(deps: RouteDeps, executor: GhExecutor, cacheMap: TtlCache<GitHubOverviewData>, globalCache: TtlCache<GlobalOverviewData>): void;
|
|
@@ -1,4 +1,137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 动作类路由:缓存强制刷新 + 右侧栏快捷操作(create_pr/create_issue/open_browser/open_terminal)。
|
|
3
|
+
* 共同特征:POST 触发、读少写多、无缓存语义,与概览类路由的演进方向不同。
|
|
4
|
+
*/
|
|
5
|
+
import { spawn } from 'node:child_process';
|
|
1
6
|
import { isSafeExternalUrl, sendJson } from './route-helpers.js';
|
|
7
|
+
/**
|
|
8
|
+
* 构造「在新终端窗口运行命令」的平台命令(execFile 数组形态,不经 shell)。
|
|
9
|
+
*
|
|
10
|
+
* Issue #2 修复要点:
|
|
11
|
+
* - Windows:cmd.exe(显式 .exe,规避 GUI 父进程 PATH 解析差异)+ start 的窗口标题
|
|
12
|
+
* 占位 "DSH GitHub"——start 的首个带引号参数会被解析为标题而非程序,占位后
|
|
13
|
+
* powershell.exe 才能被稳定识别为目标程序;
|
|
14
|
+
* - macOS:osascript 驱动 Terminal.app,命令经引号转义拼入 AppleScript;
|
|
15
|
+
* - Linux:发行版终端二进制与参数形态各异(gnome-terminal 用 `--`、konsole 用
|
|
16
|
+
* `-e`、xfce4-terminal 用 `-x`),单一硬编码在主流桌面大概率不存在——由
|
|
17
|
+
* detectLinuxTerminal 按探测结果返回对应形态。
|
|
18
|
+
*/
|
|
19
|
+
export function buildTerminalCommand(command) {
|
|
20
|
+
if (process.platform === 'win32') {
|
|
21
|
+
return {
|
|
22
|
+
bin: 'cmd.exe',
|
|
23
|
+
// -NoProfile 是 Windows 兼容基线:powershell.exe 是全量自带的 5.1,其默认执行
|
|
24
|
+
// 策略常为 Restricted——加载用户 profile(如 conda init 写入的)会就地报
|
|
25
|
+
// UnauthorizedAccess 红错(实测:winget 安装仍正常,但启动噪音吓人)。
|
|
26
|
+
// 白名单命令(gh/winget/brew/apt)均不依赖用户 profile 环境;pwsh 7 是个人
|
|
27
|
+
// 安装不作依赖,-NoProfile 在 5.1/7 语义一致,一并消除该类报错并加速启动。
|
|
28
|
+
args: ['/c', 'start', 'DSH GitHub', 'powershell.exe', '-NoExit', '-NoProfile', '-Command', command],
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
if (process.platform === 'darwin') {
|
|
32
|
+
// AppleScript 字符串内的双引号以 \" 转义;白名单命令虽无引号,仍统一转义防未来扩展踩坑
|
|
33
|
+
const escaped = command.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
|
|
34
|
+
return {
|
|
35
|
+
bin: 'osascript',
|
|
36
|
+
args: [
|
|
37
|
+
'-e', `tell application "Terminal" to do script "${escaped}"`,
|
|
38
|
+
'-e', 'tell application "Terminal" to activate',
|
|
39
|
+
],
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
if (process.platform === 'linux') {
|
|
43
|
+
const terminal = detectLinuxTerminal();
|
|
44
|
+
if (!terminal)
|
|
45
|
+
return null;
|
|
46
|
+
// 各终端「执行命令并保持窗口」的参数形态不同:
|
|
47
|
+
// - gnome-terminal -- <sh> -c:新版 -e 已弃用,-- 后直接跟命令行
|
|
48
|
+
// - konsole/xfce4-terminal/xterm:-e/-x 执行,sh -c + exec 保交互语义
|
|
49
|
+
// (命令结束后 exec sh 落回交互 shell,窗口不闪退)
|
|
50
|
+
const shArgs = ['sh', '-c', `${command}; exec sh`];
|
|
51
|
+
switch (terminal) {
|
|
52
|
+
case 'gnome-terminal': return { bin: 'gnome-terminal', args: ['--', ...shArgs] };
|
|
53
|
+
case 'konsole': return { bin: 'konsole', args: ['-e', ...shArgs] };
|
|
54
|
+
case 'xfce4-terminal': return { bin: 'xfce4-terminal', args: ['-x', ...shArgs] };
|
|
55
|
+
case 'x-terminal-emulator': return { bin: 'x-terminal-emulator', args: ['-e', ...shArgs] };
|
|
56
|
+
case 'xterm': return { bin: 'xterm', args: ['-e', ...shArgs] };
|
|
57
|
+
default: return null;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
/** Linux 终端探测结果缓存(进程级):which 探测有子进程成本,会话内只做一次 */
|
|
63
|
+
let cachedLinuxTerminal;
|
|
64
|
+
/**
|
|
65
|
+
* 探测当前 Linux 桌面可用的终端模拟器(按桌面占有率排序)。
|
|
66
|
+
* 返回 null 表示全部不可用——调用方据此走失败分支而非硬编码赌一个。
|
|
67
|
+
*/
|
|
68
|
+
function detectLinuxTerminal() {
|
|
69
|
+
if (cachedLinuxTerminal !== undefined)
|
|
70
|
+
return cachedLinuxTerminal;
|
|
71
|
+
const candidates = ['gnome-terminal', 'konsole', 'xfce4-terminal', 'x-terminal-emulator', 'xterm'];
|
|
72
|
+
try {
|
|
73
|
+
const { execFileSync } = require('node:child_process');
|
|
74
|
+
for (const name of candidates) {
|
|
75
|
+
try {
|
|
76
|
+
execFileSync('which', [name], { stdio: 'ignore', timeout: 2_000 });
|
|
77
|
+
cachedLinuxTerminal = name;
|
|
78
|
+
return name;
|
|
79
|
+
}
|
|
80
|
+
catch { /* 该候选不存在,试下一个 */ }
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
catch { /* which 本身不可用(极端环境),视为无可用终端 */ }
|
|
84
|
+
cachedLinuxTerminal = null;
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
/** 测试辅助:重置 Linux 终端探测缓存(便于单测注入不同探测环境) */
|
|
88
|
+
export function resetLinuxTerminalCache() {
|
|
89
|
+
cachedLinuxTerminal = undefined;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* 执行终端唤起并等待真实结果(Issue #2 核心修复:不再先斩后奏报假成功)。
|
|
93
|
+
* start/osascript 场景下命令随宿主立即退出,close 即代表唤起动作用的成败;
|
|
94
|
+
* err 与 stderr 原样上抛给调用方决定 HTTP 语义。
|
|
95
|
+
*/
|
|
96
|
+
function launchTerminalAndWait(command) {
|
|
97
|
+
return new Promise((resolve) => {
|
|
98
|
+
const built = buildTerminalCommand(command);
|
|
99
|
+
if (!built) {
|
|
100
|
+
resolve({ ok: false, error: '未找到可用的系统终端(Linux 下请先安装 gnome-terminal / konsole 等任一终端模拟器)' });
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
// stdio 必须忽略:start 唤起的交互式窗口会继承 execFile 的 stdout/stderr 管道句柄,
|
|
104
|
+
// -NoExit 窗口存活期间管道永不关闭——回调要等满 10s 超时甚至不触发(实测复现:
|
|
105
|
+
// 唤起成功的窗口让 HTTP 响应挂起 10s+)。交互终端本就不该捕获输出;stdio ignore
|
|
106
|
+
// 后等待语义回归 cmd.exe 自身退出(start 创建窗口后立即返回,亚秒级)。
|
|
107
|
+
// 改用原生 spawn:execFile 的类型与实现都强制 stdout 管道捕获,而交互终端唤起
|
|
108
|
+
// 场景需要的恰恰是「不捕获」——spawn 的 stdio:'ignore' 是官方支持的干净形态。
|
|
109
|
+
const child = spawn(built.bin, built.args, {
|
|
110
|
+
stdio: 'ignore',
|
|
111
|
+
detached: false,
|
|
112
|
+
});
|
|
113
|
+
// 10s 兜底超时:正常路径 cmd.exe start 毫秒级返回;超时视为唤起故障并如实回传
|
|
114
|
+
let timedOut = false;
|
|
115
|
+
const killTimer = setTimeout(() => { timedOut = true; child.kill(); }, 10_000);
|
|
116
|
+
child.on('error', (err) => {
|
|
117
|
+
clearTimeout(killTimer);
|
|
118
|
+
resolve({ ok: false, error: err.message });
|
|
119
|
+
});
|
|
120
|
+
child.on('close', (code) => {
|
|
121
|
+
clearTimeout(killTimer);
|
|
122
|
+
if (code === 0) {
|
|
123
|
+
resolve({ ok: true });
|
|
124
|
+
}
|
|
125
|
+
else if (timedOut) {
|
|
126
|
+
// kill 触发的 close 是 code=null——如实归因为超时而非"退出码 null"
|
|
127
|
+
resolve({ ok: false, error: '终端启动命令 10 秒未返回(可能被安全软件拦截)' });
|
|
128
|
+
}
|
|
129
|
+
else {
|
|
130
|
+
resolve({ ok: false, error: `终端启动命令退出码 ${code}` });
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
}
|
|
2
135
|
export function registerActionRoutes(deps, executor, cacheMap, globalCache) {
|
|
3
136
|
const { bindRoute } = deps;
|
|
4
137
|
// 1. 强制刷新缓存
|
|
@@ -80,26 +213,17 @@ export function registerActionRoutes(deps, executor, cacheMap, globalCache) {
|
|
|
80
213
|
if (!allowedCommands.includes(command)) {
|
|
81
214
|
throw new Error(`不支持唤起该指令: ${command}`);
|
|
82
215
|
}
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
if (
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
args = ['-e', `tell application "Terminal" to do script "${command}"`, '-e', 'tell application "Terminal" to activate'];
|
|
93
|
-
}
|
|
94
|
-
else {
|
|
95
|
-
bin = 'x-terminal-emulator';
|
|
96
|
-
args = ['-e', command];
|
|
216
|
+
// Issue #2 修复:等待真实唤起结果再回传——旧实现 execFile 异步回调吞错、
|
|
217
|
+
// 路由先行返回 ok:true,导致终端弹不弹都提示成功。
|
|
218
|
+
const launch = await launchTerminalAndWait(command);
|
|
219
|
+
if (!launch.ok) {
|
|
220
|
+
sendJson(res, 500, {
|
|
221
|
+
ok: false,
|
|
222
|
+
message: `唤起终端失败:${launch.error}。请手动打开系统终端执行:${command}`,
|
|
223
|
+
});
|
|
224
|
+
return;
|
|
97
225
|
}
|
|
98
|
-
|
|
99
|
-
if (err)
|
|
100
|
-
console.warn('[dsh-github-flow] 唤起终端失败:', err?.message || err);
|
|
101
|
-
});
|
|
102
|
-
sendJson(res, 200, { ok: true, message: '已请求唤起系统终端' });
|
|
226
|
+
sendJson(res, 200, { ok: true, message: '已唤起系统终端' });
|
|
103
227
|
break;
|
|
104
228
|
}
|
|
105
229
|
default:
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { loadUpdateSettings, saveUpdateSettings, isValidUpdateMode, describeUpdateMode } from '../update-settings.js';
|
|
2
|
+
import { sendJson, readJsonBody } from './route-helpers.js';
|
|
2
3
|
export function registerLifecycleRoutes(deps, executor, checker, upgrader) {
|
|
3
4
|
const { bindRoute } = deps;
|
|
4
5
|
// 1. 闲时静默检查更新端点(前端在 gh 业务数据完全拉取就绪后,由 idle 延时静默触发)
|
|
@@ -31,8 +32,12 @@ export function registerLifecycleRoutes(deps, executor, checker, upgrader) {
|
|
|
31
32
|
return;
|
|
32
33
|
}
|
|
33
34
|
try {
|
|
35
|
+
// 一键升级是用户显式动作,不受 checkUpdate 配置熔断:check() 在禁用时
|
|
36
|
+
// 会返回 latestVersion 缺失的兜底态,此时回退 'latest' 让 pnpm 自行解析
|
|
37
|
+
// 最新版(与 UpgradeManager 自身的 targetVersion 可选语义一致)。
|
|
34
38
|
const info = await checker.check(false);
|
|
35
|
-
const
|
|
39
|
+
const target = info.latestVersion || 'latest';
|
|
40
|
+
const result = await upgrader.executeUpgrade(target);
|
|
36
41
|
sendJson(res, result.ok ? 200 : 500, result);
|
|
37
42
|
}
|
|
38
43
|
catch (err) {
|
|
@@ -52,6 +57,52 @@ export function registerLifecycleRoutes(deps, executor, checker, upgrader) {
|
|
|
52
57
|
sendJson(res, 200, { ok: true, data: upgrader.getStatus() });
|
|
53
58
|
},
|
|
54
59
|
});
|
|
60
|
+
// 5. 更新源设置:GET 读取(store 权威,缺省 official)/ POST 保存并立即用新源验证
|
|
61
|
+
bindRoute({
|
|
62
|
+
kind: 'exact',
|
|
63
|
+
path: '/api/github/update-settings',
|
|
64
|
+
handler: async (req, res) => {
|
|
65
|
+
if (req.method === 'GET') {
|
|
66
|
+
const mode = loadUpdateSettings();
|
|
67
|
+
sendJson(res, 200, { ok: true, data: { mode, description: describeUpdateMode(mode) } });
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
if (req.method !== 'POST') {
|
|
71
|
+
sendJson(res, 405, { ok: false, message: '仅支持 GET / POST 请求' });
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
try {
|
|
75
|
+
const body = await readJsonBody(req);
|
|
76
|
+
const mode = body?.mode;
|
|
77
|
+
if (!isValidUpdateMode(mode)) {
|
|
78
|
+
sendJson(res, 400, { ok: false, message: `非法更新源模式: ${String(mode)}(允许 official / mirror / off)` });
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
// 写盘失败不阻断内存态生效(save 内部已 warn),返回携带 error 提示重启后需重设
|
|
82
|
+
const saved = saveUpdateSettings(mode);
|
|
83
|
+
checker.applySettings(mode);
|
|
84
|
+
// 非 off 模式:立即用新源真探一次,把结果带回前端展示(缓存已被 applySettings 失效)
|
|
85
|
+
let probe = null;
|
|
86
|
+
if (mode !== 'off') {
|
|
87
|
+
const info = await checker.check(true);
|
|
88
|
+
probe = { latestVersion: info.latestVersion, hasUpdate: info.hasUpdate, checkedAt: info.checkedAt };
|
|
89
|
+
}
|
|
90
|
+
sendJson(res, 200, {
|
|
91
|
+
ok: true,
|
|
92
|
+
data: {
|
|
93
|
+
mode,
|
|
94
|
+
description: describeUpdateMode(mode),
|
|
95
|
+
persisted: saved.ok,
|
|
96
|
+
persistError: saved.error,
|
|
97
|
+
probe,
|
|
98
|
+
},
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
catch (err) {
|
|
102
|
+
sendJson(res, 500, { ok: false, message: err.message || '保存更新源设置失败' });
|
|
103
|
+
}
|
|
104
|
+
},
|
|
105
|
+
});
|
|
55
106
|
// 4. 快速认证状态轻量探针 (耗时 < 500ms,专供登录自愈检测与快速恢复)
|
|
56
107
|
bindRoute({
|
|
57
108
|
kind: 'exact',
|