dsh-recall-plugin 1.0.2 → 1.0.3

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.
Files changed (2) hide show
  1. package/lib/index.js +46 -7
  2. package/package.json +1 -1
package/lib/index.js CHANGED
@@ -83,16 +83,35 @@ export function apply(ctx) {
83
83
  const sp = ctx.get('sandboxPolicy')
84
84
  if (sp && sp.workspaceRoot) root = sp.workspaceRoot
85
85
  }
86
+ // 规范化尾部反斜杠(保留 "D:\" 三字符盘根形态,去掉会变成无效的 "D:"):
87
+ // cwd 是否带尾斜杠由上游决定,不归一会让哈希输入不一致(换 store 目录),
88
+ // 也会让大文件排除扫描的 Substring($root.Length + 1) 相对路径错一位。
89
+ if (root && root.length > 3) root = root.replace(/\\+$/, '')
86
90
  if (root) state.roots.set(key, root)
87
91
  return root
88
92
  }
89
93
 
90
94
  // 解析 git 可执行文件路径:DSH 进程 PATH 可能不含 git,
91
95
  // 求值一次并缓存,脚本里用绝对路径调用,避免每条命令依赖 PATH。
96
+ // 候选覆盖 PATH / 64 位 / 32 位 / 用户级(LocalAppData)四类安装位置,
97
+ // 并用 -PathType Leaf 挡掉 PATH 里恰有名为 git 的目录这种极端情形。
92
98
  async function resolveGit() {
93
99
  if (state.gitExe !== null) return state.gitExe
94
100
  try {
95
- let path = stripBom(await runShell("$g = (Get-Command git -ErrorAction SilentlyContinue).Source; if (-not $g -and (Test-Path -LiteralPath 'C:\\Program Files\\Git\\cmd\\git.exe')) { $g = 'C:\\Program Files\\Git\\cmd\\git.exe' }; if ($g) { Write-Output $g }", { stdoutMaxBytes: 4096 })).trim()
101
+ // 逐项判空再 Join-Path:个别 env 在特殊环境(32 位系统无
102
+ // ProgramFiles(x86))取到 null,EAP=Stop 下 Join-Path 抛错会
103
+ // 让整个探测失败、误报 gitMissing。
104
+ const script = [
105
+ '$candidates = @()',
106
+ '$g = (Get-Command git -ErrorAction SilentlyContinue).Source',
107
+ 'if ($g) { $candidates += $g }',
108
+ "if (${env:ProgramFiles}) { $candidates += (Join-Path ${env:ProgramFiles} 'Git\\cmd\\git.exe') }",
109
+ "if (${env:ProgramFiles(x86)}) { $candidates += (Join-Path ${env:ProgramFiles(x86)} 'Git\\cmd\\git.exe') }",
110
+ "if (${env:LocalAppData}) { $candidates += (Join-Path ${env:LocalAppData} 'Programs\\Git\\cmd\\git.exe') }",
111
+ "$g = $candidates | Where-Object { $_ -and (Test-Path -LiteralPath $_ -PathType Leaf) } | Select-Object -First 1",
112
+ 'if ($g) { Write-Output $g }'
113
+ ].join('\n')
114
+ const path = stripBom(await runShell(script, { stdoutMaxBytes: 4096 })).trim()
96
115
  state.gitExe = path || ''
97
116
  } catch (error) {
98
117
  state.gitExe = ''
@@ -105,9 +124,13 @@ export function apply(ctx) {
105
124
  // 后两者是 .NET 5+(仅 PS 7)API,别人机器的 shell 若是 Windows PowerShell
106
125
  // 5.1 会抛错,导致 home 存储永远降级到项目内;前者两个版本都可用。
107
126
  async function homeDirFor(root, sessionId) {
127
+ // DSH 的 pwsh 子进程按白名单重建 env,会话级导出的 DSH_HOME 传不进去;
128
+ // 主进程(node)仍能看到它,作为字面量回退注入,保证「DSH_HOME 指到哪、
129
+ // 快照就存哪」在任意导出层级下都成立。
130
+ const envHome = (process.env && process.env.DSH_HOME) || ''
108
131
  const dirScript = [
109
132
  '$r = ' + psq(root),
110
- '$h = if ($env:DSH_HOME) { $env:DSH_HOME } else { Join-Path $env:USERPROFILE ".dsh" }',
133
+ "$h = if ($env:DSH_HOME) { $env:DSH_HOME } elseif (" + psq(envHome) + ") { " + psq(envHome) + " } else { Join-Path $env:USERPROFILE \".dsh\" }",
111
134
  '$sha = [Security.Cryptography.SHA256]::Create()',
112
135
  "$hex = ([BitConverter]::ToString($sha.ComputeHash([Text.Encoding]::UTF8.GetBytes($r))) -replace '-','').ToLower()",
113
136
  "Write-Output (Join-Path $h ('dsh-recall-snapshots\\' + $hex))"
@@ -260,7 +283,10 @@ export function apply(ctx) {
260
283
  dropGitlinksScript(),
261
284
  '& $git --git-dir=$g --work-tree=$root add -A',
262
285
  dropGitlinksScript(),
263
- 'Get-ChildItem -LiteralPath $root -Recurse -File -Force | Where-Object { $_.Length -gt ' + MAX_FILE_BYTES + ' } | ForEach-Object {',
286
+ // 扫描加 SilentlyContinue:EAP=Stop 下个别不可访问子目录(杀软锁定、
287
+ // 异常 ACL、损坏 junction)的非致命错误会被升级为终止,整条快照作废;
288
+ // 本扫描只用于排除超大文件,漏看个别文件是 fail-open,可接受。
289
+ 'Get-ChildItem -LiteralPath $root -Recurse -File -Force -ErrorAction SilentlyContinue | Where-Object { $_.Length -gt ' + MAX_FILE_BYTES + ' } | ForEach-Object {',
264
290
  " $rel = $_.FullName.Substring($root.Length + 1).Replace('\\','/')",
265
291
  ' & $git --literal-pathspecs --git-dir=$g update-index --force-remove -- $rel',
266
292
  '}',
@@ -290,7 +316,7 @@ export function apply(ctx) {
290
316
  dropGitlinksScript(),
291
317
  '& $git --git-dir=$g --work-tree=$root add -A',
292
318
  dropGitlinksScript(),
293
- 'Get-ChildItem -LiteralPath $root -Recurse -File -Force | Where-Object { $_.Length -gt ' + MAX_FILE_BYTES + ' } | ForEach-Object {',
319
+ 'Get-ChildItem -LiteralPath $root -Recurse -File -Force -ErrorAction SilentlyContinue | Where-Object { $_.Length -gt ' + MAX_FILE_BYTES + ' } | ForEach-Object {',
294
320
  " $rel = $_.FullName.Substring($root.Length + 1).Replace('\\','/')",
295
321
  ' & $git --literal-pathspecs --git-dir=$g update-index --force-remove -- $rel',
296
322
  '}',
@@ -342,7 +368,7 @@ export function apply(ctx) {
342
368
  dropGitlinksScript(),
343
369
  '& $git --git-dir=$g --work-tree=$root add -A',
344
370
  dropGitlinksScript(),
345
- 'Get-ChildItem -LiteralPath $root -Recurse -File -Force | Where-Object { $_.Length -gt ' + MAX_FILE_BYTES + ' } | ForEach-Object {',
371
+ 'Get-ChildItem -LiteralPath $root -Recurse -File -Force -ErrorAction SilentlyContinue | Where-Object { $_.Length -gt ' + MAX_FILE_BYTES + ' } | ForEach-Object {',
346
372
  " $rel = $_.FullName.Substring($root.Length + 1).Replace('\\','/')",
347
373
  ' & $git --literal-pathspecs --git-dir=$g update-index --force-remove -- $rel',
348
374
  '}',
@@ -390,7 +416,11 @@ export function apply(ctx) {
390
416
  ].join('\n')
391
417
  }
392
418
 
393
- // 索引写入合并为单次 shell 调用:pwsh 进程启动是主要耗时,能省一次是一次
419
+ // 索引写入合并为单次 shell 调用:pwsh 进程启动是主要耗时,能省一次是一次。
420
+ // base64 以内联字面量传递时受 Windows 命令行 32767 字符硬上限约束(DSH 的
421
+ // pwsh 执行器把命令串作为 -Command 的单个 argv 元素 spawn),快照攒到几百条
422
+ // 就会超限 spawn 失败——按 20000 字符分块,首块 Set-Content、续块 Add-Content,
423
+ // 常规体量仍是单次调用,超限后自动多写几块。
394
424
  async function saveIndex(root, sessionId) {
395
425
  const store = state.stores.get(root)
396
426
  if (!store) return
@@ -400,7 +430,16 @@ export function apply(ctx) {
400
430
  const json = JSON.stringify(entries)
401
431
  try {
402
432
  const b64 = Buffer.from(json, 'utf8').toString('base64')
403
- await runShell("New-Item -ItemType Directory -Force -Path " + psq(store.dir) + " | Out-Null; [Text.Encoding]::UTF8.GetString([Convert]::FromBase64String('" + b64 + "')) | Set-Content -LiteralPath " + psq(store.dir + '\\index.json') + ' -Encoding utf8 -NoNewline', { stdoutMaxBytes: 4096 })
433
+ const file = psq(store.dir + '\\index.json')
434
+ let first = true
435
+ for (let i = 0; i < b64.length; i += 20000) {
436
+ const piece = "[Text.Encoding]::UTF8.GetString([Convert]::FromBase64String('" + b64.slice(i, i + 20000) + "')) | "
437
+ const cmd = first
438
+ ? 'New-Item -ItemType Directory -Force -Path ' + psq(store.dir) + ' | Out-Null; ' + piece + 'Set-Content -LiteralPath ' + file + ' -Encoding utf8 -NoNewline'
439
+ : piece + 'Add-Content -LiteralPath ' + file + ' -Encoding utf8 -NoNewline'
440
+ await runShell(cmd, { stdoutMaxBytes: 4096 })
441
+ first = false
442
+ }
404
443
  } catch (error) {
405
444
  console.error('recall saveIndex failed:', String(error))
406
445
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-recall-plugin",
3
- "version": "1.0.2",
3
+ "version": "1.0.3",
4
4
  "description": "DSH 消息撤回插件:在用户消息气泡旁加「撤回」按钮,把项目文件(独立影子 git 仓库快照)与对话历史(官方 fork)一并回退到该消息发送之前。",
5
5
  "type": "module",
6
6
  "repository": {