Skip to content

fix(npm-resolve): 按仓库名猜名反查,低热度包不再误判为「无 npm 包」 - #64

Closed
Fishquito7 wants to merge 1 commit into
dshplugin:mainfrom
Fishquito7:fix/npm-reverse-lookup-query
Closed

Fishquito7 wants to merge 1 commit into
dshplugin:mainfrom
Fishquito7:fix/npm-reverse-lookup-query

Conversation

@Fishquito7

Copy link
Copy Markdown

问题

目录没有下发 npmPackage 时,服务端会用 npm registry 的 search 接口按 repository 地址反查官方包名(resolveNpmPackage)。但 npm search 并没有 repository: 限定符 —— 它被当作普通词元参与全文检索,结果按热度排序。

于是只发 repository:owner/name 这一条查询时,「包名就等于仓库名、但下载量低」的插件根本进不了 size=10 的第一页,反查得出「该仓库没有 npm 包」的错误结论并把它缓存下来,安装永远回落到 git 通道 —— 而 git 通道恰恰是缺构建产物 / 需要认证 / 用户网络受限时最容易失败的那一条。

实例:fishquito7/dsh-skill-mcp-panel(包名与仓库名一致,热度很低)反查不到,只能走 git。

改动

src/server/services/install/npm-resolve.ts

  • 新增 npmResolveQueries(owner, name):按 仓库名 → 作者名 → repository:owner/name 的顺序猜名,命中即返回。repository: 写法降级为最后一条,只为兼容真把它当限定符实现的镜像源。
  • 单条查询的 size 从 10 提到 25,让铁证校验能看见热度排名靠后的候选。
  • 每条结果仍要过原有的铁证校验(包元数据必须指回该仓库),所以检索词放宽不会误命中。
  • 只要有任何一条查询没能给出答案(网络 / 超时 / 解析失败)就返回 undefined 而不是 null —— 避免把一次不确切的结论缓存成「确认无包」,那会让整个会话都退回 git 通道。
  • repo 段数校验收紧到恰好两段(a/b/c 直接返回 null,不再发无谓请求)。

测试

  • tests/npm-resolve.test.ts
    • 新增联网用例:resolveNpmPackage('fishquito7/dsh-skill-mcp-panel') === 'dsh-skill-mcp-panel'。
    • 新增无网用例:锁住检索词顺序(首条必须是仓库名)。
  • 本机:npm run typecheck:server 通过;npm test → 86 tests / 84 pass / 0 fail / 2 skipped。

一个相关观察(未在本 PR 中改动)

反查修好后,「低热度包」会开始走 npm 通道,而 npm 通道传的是裸包名(dsh plugin add <pkg>)。DSH profile 目录里有 pnpm-workspace.yaml,pnpm 的发布年龄门会让刚发布不久的版本被跳过 —— 本机 pnpm 11.7.0 实测:

目录状态 pnpm add dsh-skill-mcp-panel 解析到的版本
profile 形态(有 pnpm-workspace.yaml,无该键) 2.1.0
同上 + minimumReleaseAge: 0 2.1.2(registry latest)
同上 + minimumReleaseAge: 1 2.1.2

也就是说:发版后的 24 小时内,npm 通道会装到上一个版本。对 dsh-skill-mcp-panel 来说这曾经是致命的(2.1.0 在 DSH 0.1.7 上技能页白屏),其他插件也可能踩同样的坑。

可选做法(属于安装通道的设计取舍,交给维护者定夺,我故意没有塞进本 PR):把 npm 通道的 target 带上目录里的版本号(pkg@x.y.z),或在文档里把 npm 通道明确成「装 registry latest、可能滞后一个版本」。

不过反查本身仍是纯收益:它修掉的是「明明有 npm 包却被判成没有」这个错误结论。

npm search 接口没有 `repository:` 限定符:它被当作普通词元参与全文检索,
结果按下载量/质量分排序。只发这一条查询时,size=10 的第一页全是热门包,
「包名就等于仓库名但下载量低」的插件根本排不进去,反查因此得出
「该仓库没有 npm 包」的错误结论并把它缓存下来,安装回落到 git 通道 ——
而 git 通道正是缺构建产物、需要认证或用户网络受限时最容易失败的一条路。

改成按「仓库名 → 作者名 → repository: 兼容写法」顺序猜名,命中即返回;
每条结果仍然要过原有的铁证校验(包元数据必须指回该仓库),所以放宽检索词
不会误命中。只要有任何一条查询没能给出答案(网络/超时/解析),就返回
undefined 而不返回 null,避免把一次不确切的结论缓存成「确认无包」。

同时把 repo 段数校验收紧到恰好两段(`a/b/c` 直接返回 null,不再发无谓请求)。
@dshplugin

Copy link
Copy Markdown
Owner

感谢 PR,根因抓得准 —— 只发 repository:owner/name 一条查询,等于把结果完全交给热度排序,包名即仓库名的低热度插件确实会掉出第一页,然后被缓存成「该仓库没有 npm 包」而一直回落 git 通道。按「仓库名 → 作者名 → repository: 兼容写法」猜名、size 提到 25、把「不确定」与「确认无包」区分成 undefined / null(不缓存不确切结论),以及 repo 段数收紧到恰好两段,这几点都认同。

合并前想确认两处:

  1. 耗时预算:searchOnce 单次超时 8s,现在最多串行 3 条,最坏 24s;而 resolveNpmPackage 在安装流程里是 await 的(src/server/http/routes.ts:783),弱网下这段延迟直接加在「点安装」到「开始装」之间。能否给整体加个预算 —— 比如三条查询共享一个总超时,或后续查询只在必要时才发?
    1. 失败报告里的命令文案:src/server/http/routes.ts:786/792 仍固定写 npm search repository:<repo>。改为按序猜名后,实际命中的多半是第一条(裸仓库名),但失败提 Issue 时作者看到的仍是 repository: 那条 —— 这条文案的用途正是让作者一眼指认正确包名,建议随本 PR 一起改成实际按序尝试的检索词。
      这两处调整后就没有其它顾虑了。另外你附带的 minimumReleaseAge 观察(npm 通道会装到滞后一个版本)很有价值,会单独跟进。

@dshplugin dshplugin closed this Sep 26, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants