dsh-gitbash-shell 0.40.3 → 0.40.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/CHANGELOG.md CHANGED
@@ -3,6 +3,28 @@
3
3
  > 倒序排列,新版本条目在最上面。条目格式:`## vX.Y.Z — YYYY-MM-DD` + 类型(feat / fix / docs / chore)+ 要点 + 相关链接。
4
4
  > 纪律见 AGENTS.md「变更记录纪律」:发版前先更新本文件并随版本提交;事故复盘、复现与真机验证记录也记在这里。
5
5
 
6
+ ## v0.40.5 — 2026-10-18
7
+
8
+ ### 工具行字号跟随宿主字体设置;半像素字号归整(fix)
9
+
10
+ - **内容面接官方 delta**:工具行可见面接 `--dsh-content-font-delta-secondary`(官方派生变量 = 宿主「设置 → 字体大小」正文值 − 1px 再钳制,非独立设置项)——`.gb-toolRow`(12px 基准)、`.gb-toolState` / `.gb-toolSrc`(11px 基准)、`.gb-toolResult` / `.gb-toolFail`(基准 11.5→12px)。用户把正文字号调大后,工具行、状态徽标、来源路径、结果与失败说明同步放大,不再钉死。
11
+ - **半像素归整官方档位**:官方字号体系没有 x.5 值——`.gb-sectionTitle` 12.5→12px(设置卡 chrome 标题,保持固定不接 delta,与官方设置卡一致);`.gb-toolResult` / `.gb-toolFail` 11.5→12px 基准(默认观感 +0.5px,换来 delta 跟随)。
12
+ - 调查结论(四插件联合排查,2026-10-18):官方 ui-deliverables 的 IDE 类面板本身全部固定字号、零 delta,设置卡 chrome 固定亦合规;本插件只有上述工具行属于「内容随设置缩放」范畴,其余小字(偏移、徽标)为有意设计不动。
13
+ - 验证:`npm test` 236 项全绿(1 skip 为既有基线);CSS_TEXT 内 delta 引用 5 处、半像素残留 0;真机双端验证待用户复验(改后默认 14px 设置下视觉基本无变化,调大后生效)。
14
+
15
+ ## v0.40.4 — 2026-10-17
16
+
17
+ ### 终端工具扫描只认可执行条目:手册页/文档/同名目录不再冒充命令(fix)
18
+
19
+ - `scanCommands`(`src/toolchain.js`)原先用 `entry.replace(/\.[^./\\]+$/, '')` 剥掉**任意**扩展名,于是 PATH 目录里任何与工具同名的条目都算命中:
20
+ - **真实案例**:winget 便携包目录中 `just.1`(手册页)在字典序上排在 `just.exe` 之前,而扫描是「首个命中者胜出」⇒ 卡片「完整路径 / 由谁提供」列把手册页当成了 `just` 的可执行文件(实测 `commandPath` = `…\Casey.Just_Microsoft.Winget.Source_8wekyb3d8bbwe\just.1`)。
21
+ - **判定层影响**:`classifyToolState` 在 `wingetOwned === false` 时只看 `hasCommand`,所以一台**确实没装**该命令、而 PATH 里恰好有同名文档 / 手册页 / 目录的机器,该行会被判成 `external`(`isActionable` 为 false)——**安装入口永不出现**。本机 `just` 之所以未暴露,只是 winget 清单命中在前把它掩盖了。
22
+ - 修法(**纯函数改动、零 io 变更**):新增扩展名白名单 `EXEC_EXTS = {.exe,.cmd,.bat,.com}`,扩展名不在白名单的条目直接跳过;**无扩展名照旧命中**——Git for Windows 自带 `/usr/bin/make` 这类无扩展名可执行文件(`fzf` 同理),这条不能丢。未加 `.` / `..` 防御分支:`readdirSync` 本就不返回这两项。
23
+ - 回归用例(`tests/smoke.mjs`「only executable extensions resolve」)锁三层:手册页 / 文档 / 目录不命中;无扩展名真可执行仍命中;`just.1` 排在 `just.exe` 之前时**首个可执行命中者**路径正确(另覆盖 `.EXE` 大小写)。判定层补一条联动断言:扫不到 ⇒ `missing` ⇒ `isActionable` 为真。**该用例已在未修复的源码上验证会失败**(`actual: 'C:/pkg\just.1'`),修复后通过。
24
+ - 目录区分(无扩展名目录 vs 无扩展名 MSYS 可执行文件)**留作后续**:需要给 `io.readdir` 增加 `stat`/`Dirent` 能力并牵动调用方,本版不动 io 契约。
25
+ - 验证:Windows 10.0.26200 / Node 24.19.0,`npm test` 通过(当时基线计数把 skip 计入记作 237;维护者复核:基线 235 pass + 1 skip,合并分支 236 pass + 1 skip,本用例恰好多 1)。维护者另在 macOS / Node 24 复跑全量同绿,CI ubuntu+windows × Node 22/24 四矩阵绿。
26
+ - 相关:[issue #31](https://github.com/KannaKuron/dsh-gitbash-shell/issues/31)。
27
+
6
28
  ## v0.40.3 — 2026-10-17
7
29
 
8
30
  ### gitBash 能力服务新增 `pwshToolEnabled`(feat,additive——配对 dsh-ptc-cordis-preset v0.20.1)
package/dsh.plugin.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "id": "dsh-external/dsh-gitbash-shell",
3
- "version": "0.40.3",
3
+ "version": "0.40.5",
4
4
  "main": "./src/index.js",
5
5
  "description": "Windows 全模式 Git Bash:自带 bash 工具(私有 Git Bash 执行器实例,官方 PowerShell 执行器原样保留),官方四个模式经行内工具补位直接获得 Git Bash(v0.35.0 起;v0.39.0 起不再替换宿主执行器),四个 *·Git Bash 变体 preset 可开关;官方 pwsh 工具默认隐藏、开关可见(真 PowerShell);附带 gb_* FastCtx 文件工具,检测到 dsh-ops 自动让位。联动:dsh-ptc-cordis-preset(PTC 创造模式带 Git Bash 行且可去重)+ dsh-better-sidebar(终端标签自动切 Git Bash)。",
6
6
  "engines": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-gitbash-shell",
3
- "version": "0.40.3",
3
+ "version": "0.40.5",
4
4
  "description": "Windows 上给 agent 一个真正的 Git Bash 命令工具:私有 Git Bash 执行器实例(官方 PowerShell 执行器原样保留),统一 MSYS 盘根路径方言,官方 pwsh 工具默认隐藏可开关,物化四个内置预设的 Git Bash 变体,侧栏终端接管可开关。· Give agents a real Git Bash tool on Windows: a private Git Bash executor instance (the official PowerShell executor stays untouched), a unified MSYS drive-root path dialect, an opt-in official pwsh tool, and four Git Bash preset variants.",
5
5
  "type": "module",
6
6
  "repository": {
package/src/client.js CHANGED
@@ -1861,7 +1861,7 @@ window.__ModuleLoader__.load({
1861
1861
  ".gb-modalTitle{font-size:15px;font-weight:600;color:var(--dsw-alias-label-primary)}",
1862
1862
  ".gb-modalList{margin:0;padding-left:18px;font-size:12px;line-height:1.6;color:var(--dsw-alias-label-tertiary);word-break:break-all}",
1863
1863
  ".gb-input{flex:1;min-width:0;padding:6px 8px;font-size:12px;border-radius:8px;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1,transparent);color:var(--dsw-alias-label-primary)}",
1864
- ".gb-sectionTitle{font-size:12.5px;font-weight:600;letter-spacing:.02em;color:var(--dsw-alias-label-secondary)}",
1864
+ ".gb-sectionTitle{font-size:12px;font-weight:600;letter-spacing:.02em;color:var(--dsw-alias-label-secondary)}",
1865
1865
  ".gb-input{flex:1;min-width:0;appearance:none;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-primary);font:inherit;font-size:13px;padding:6px 10px;outline:none}",
1866
1866
  ".gb-input:focus{border-color:var(--dsw-alias-brand-primary)}",
1867
1867
  ".gb-saveBtn{appearance:none;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-secondary);font:inherit;font-size:13px;padding:6px 12px;cursor:pointer;transition:border-color .16s,color .16s}",
@@ -1871,15 +1871,15 @@ window.__ModuleLoader__.load({
1871
1871
  ".gb-stack{display:flex;flex-direction:column;gap:8px}",
1872
1872
  /* terminal toolchain (v0.30.0) */
1873
1873
  ".gb-tools{display:flex;flex-direction:column;max-height:260px;overflow:auto;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;padding:4px 8px;background:var(--dsw-alias-bg-layer-1,transparent)}",
1874
- ".gb-toolRow{display:flex;align-items:center;gap:8px;font-size:12.5px;line-height:1.95;color:var(--dsw-alias-label-secondary);cursor:pointer}",
1874
+ ".gb-toolRow{display:flex;align-items:center;gap:8px;font-size:calc(12px + var(--dsh-content-font-delta-secondary, 0px));line-height:1.95;color:var(--dsw-alias-label-secondary);cursor:pointer}",
1875
1875
  ".gb-toolRow input{flex:none;margin:0}",
1876
1876
  ".gb-toolCmd{flex:none;min-width:78px;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;color:var(--dsw-alias-label-primary)}",
1877
- ".gb-toolState{flex:none;font-size:11.5px;padding:0 6px;border-radius:6px;border:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-tertiary);white-space:nowrap}",
1877
+ ".gb-toolState{flex:none;font-size:calc(11px + var(--dsh-content-font-delta-secondary, 0px));padding:0 6px;border-radius:6px;border:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-tertiary);white-space:nowrap}",
1878
1878
  ".gb-st-missing{border-color:var(--dsw-alias-brand-primary);color:var(--dsw-alias-brand-primary)}",
1879
1879
  ".gb-st-managed{border-color:var(--dsw-alias-state-warning-primary,#d29922);color:var(--dsw-alias-state-warning-primary,#d29922)}",
1880
1880
  ".gb-st-current{border-color:var(--dsw-alias-state-success-primary,#3fb950);color:var(--dsw-alias-state-success-primary,#3fb950)}",
1881
- ".gb-toolSrc{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:11.5px;color:var(--dsw-alias-label-tertiary)}",
1882
- ".gb-toolResult{display:flex;gap:6px;font-size:11.5px;line-height:1.6;color:var(--dsw-alias-label-tertiary);overflow-wrap:anywhere}",
1881
+ ".gb-toolSrc{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:calc(11px + var(--dsh-content-font-delta-secondary, 0px));color:var(--dsw-alias-label-tertiary)}",
1882
+ ".gb-toolResult{display:flex;gap:6px;font-size:calc(12px + var(--dsh-content-font-delta-secondary, 0px));line-height:1.6;color:var(--dsw-alias-label-tertiary);overflow-wrap:anywhere}",
1883
1883
  ".gb-ok{flex:none;color:var(--dsw-alias-state-success-primary,#3fb950)}",
1884
1884
  ".gb-bad{flex:none;color:var(--dsw-alias-status-danger,#e5484d)}",
1885
1885
  ".gb-segBtn:disabled{opacity:.45;cursor:default}",
@@ -1895,7 +1895,7 @@ window.__ModuleLoader__.load({
1895
1895
  ".gb-toolBarFill{display:block;height:100%;width:38%;border-radius:2px;background:var(--dsw-alias-brand-primary);animation:gb-slide 1.15s ease-in-out infinite}",
1896
1896
  ".gb-jobBar{height:6px;border-radius:3px;background:var(--dsw-alias-border-l2);overflow:hidden}",
1897
1897
  ".gb-jobBarFill{display:block;height:100%;border-radius:3px;background:var(--dsw-alias-brand-primary);transition:width .3s ease}",
1898
- ".gb-toolFail{margin:0 0 4px 26px;font-size:11.5px;line-height:1.5;color:var(--dsw-alias-status-danger,#e5484d);overflow-wrap:anywhere}",
1898
+ ".gb-toolFail{margin:0 0 4px 26px;font-size:calc(12px + var(--dsh-content-font-delta-secondary, 0px));line-height:1.5;color:var(--dsw-alias-status-danger,#e5484d);overflow-wrap:anywhere}",
1899
1899
  "@keyframes gb-spin{to{transform:rotate(360deg)}}",
1900
1900
  "@keyframes gb-slide{0%{margin-left:-38%}100%{margin-left:100%}}",
1901
1901
  ].join("\n");
package/src/toolchain.js CHANGED
@@ -213,18 +213,30 @@ export function parseExportJson(text, tools) {
213
213
  return found
214
214
  }
215
215
 
216
+ /**
217
+ * The extensions a PATH entry must carry to BE the command. Anything else in a
218
+ * PATH directory — a man page, a README, a directory — merely shares the stem
219
+ * and must never resolve. An entry with NO extension still does: Git for
220
+ * Windows ships its own executables that way (`/usr/bin/make`).
221
+ */
222
+ const EXEC_EXTS = new Set(['.exe', '.cmd', '.bat', '.com'])
223
+
216
224
  /**
217
225
  * Which catalog commands the machine already has, and WHERE they came from.
218
226
  * One `readdir` per PATH directory (not one `exists` per tool × directory ×
219
227
  * extension), and every unreadable directory is skipped rather than fatal.
220
228
  * First PATH hit wins, which is the same precedence the shell itself applies.
229
+ * Only EXECUTABLE entries resolve (see EXEC_EXTS): a man page (`just.1`) or a
230
+ * document (`jq.md`) shares the stem without being the command, and issue #31
231
+ * measured a man page winning the first-hit race over the `just.exe` beside it.
221
232
  * @param {{pathDirs: () => string[], readdir: (dir: string) => string[]}} io
222
233
  * @param {Array} tools - catalog entries to look for.
223
234
  * @returns {Map<string, string>} cmd → the first resolving full path.
224
235
  */
225
236
  export function scanCommands(io, tools) {
226
237
  const list = Array.isArray(tools) ? tools : TOOLCHAIN
227
- // "make.exe" / "make.cmd" / "make" all resolve the command `make`.
238
+ // "make.exe" / "make.cmd" / "make" all resolve the command `make`; "make.1"
239
+ // (the man page) and "make.txt" do not — see EXEC_EXTS.
228
240
  const wanted = new Map(list.map((tool) => [String(tool.cmd).toLowerCase(), tool.cmd]))
229
241
  const found = new Map()
230
242
  let dirs
@@ -236,7 +248,12 @@ export function scanCommands(io, tools) {
236
248
  if (!Array.isArray(entries)) continue
237
249
  for (const entry of entries) {
238
250
  if (typeof entry !== 'string') continue
239
- const base = entry.replace(/\.[^./\\]+$/, '').toLowerCase()
251
+ // The LAST extension decides, exactly as before — but it now has to be
252
+ // an executable one, so a man page/doc/directory is skipped instead of
253
+ // resolving to a command it merely shares a stem with.
254
+ const suffix = /(\.[^./\\]+)$/.exec(entry)
255
+ if (suffix !== null && !EXEC_EXTS.has(suffix[1].toLowerCase())) continue
256
+ const base = (suffix === null ? entry : entry.slice(0, suffix.index)).toLowerCase()
240
257
  const cmd = wanted.get(base)
241
258
  if (cmd !== undefined && !found.has(cmd)) {
242
259
  found.set(cmd, dir.replace(/[\\/]+$/, '') + '\\' + entry)