dsh-m 0.9.49 → 0.9.51
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 +30 -0
- package/docs/DESIGN.md +3 -3
- package/lib/core/community-adapter.js +4 -0
- package/lib/core/host-api.js +8 -5
- package/lib/core/installed.js +3 -3
- package/lib/core/versions.js +37 -12
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,21 @@ The full release history of dsh-m, maintained bilingually: **Chinese first, Engl
|
|
|
8
8
|
|
|
9
9
|
## 中文
|
|
10
10
|
|
|
11
|
+
### 0.9.51 变更:社区条目 github 字段补全(图标覆盖 45.6% → 100%)
|
|
12
|
+
|
|
13
|
+
- **动机**:社区区 npm 源条目卡片恒为首字母色块——适配层 npm 分支只产出 `npm` 字段即定源,上游目录 `url` 里的 GitHub repo 映射只被降级进 homepage,`github` 字段恒缺,客户端 Icon 的 owner 头像兜底(`github.com/<owner>.png?size=64`)无从触发。实测 `dsh-plugin-catalog@2026.1007.4837`:4,226 条可收录条目中 npm 源 2,298 条 **100% 带 github.com url**,纯数据丢弃,非上游缺失。
|
|
14
|
+
- **改动(单点)**:适配层 npm 分支补派生 `github = githubFromUrl(url)`(子包 `/tree/` 取 repo 根、原样大小写;派生失败不产出键、不跳过条目,条目层宽松 Q43)。`source` 语义不变;客户端零改动——Icon 兜底、详情行「GitHub · npm」、已装 `registryGithub`(README 基址三级兜底第一级)既有链路自动生效。
|
|
15
|
+
- **语义激活(已在案接受)**:①合并层 github 碰撞分支开始作用于社区 npm 条目(同 repo 让位精选恒优先)——全量真实目录模拟新增让位 0 条(4,215 → 4,215);②GitHub 源手装的插件可被社区 npm 条目匹配,升级走 npm 源(与精选双源条目既有语义一致)。性能面零回归:宿主侧零新增网络请求、latest 探测路径与缓存键不变、Q46 社区 github 探测豁免与 GithubBudget 不触及。
|
|
16
|
+
- **测试**:适配层翻转 1 断言 + 净增 4 例(github url 派生与原样大小写、非 github 域、无 url、非法形状),既有子包用例补 1 断言(取 repo 根);合并层净增 2 例(github 碰撞让位、GitHub 源手装匹配,均红→绿);全量与基线对照零新增失败、typecheck 零错误。
|
|
17
|
+
|
|
18
|
+
### 0.9.50 变更:README 兜底 /latest 两腿阶梯 + 未装文案 profile-aware + 兜底失败摘要
|
|
19
|
+
|
|
20
|
+
- **动机(上海 Windows 桌面机实机实证)**:desktop profile(生效源 npmmirror)打开 billion-context(705k 周下载)详情 Modal 的 README 折叠页恒报「web profile 未安装该插件」——npmmirror packument 实测 8,586,604 字节,超过 npmPackumentReadme 的 8MB(8,388,608)上限被 readCapped 拒(`502 响应超过上限`),兜底腿必失败后按 0.9.45 语义如实重抛原始本地错误;同期 npmjs packument 8,195,089 字节恰低于帽、readme 为空,web 侧显示「没有 README」只是帽下侥幸——该包高频发版,npmjs 超帽只是时间问题。
|
|
21
|
+
- **README 兜底两腿阶梯**:`npmPackumentReadme` 内部改两腿(对外签名与返回形状 `{readme, repo}` 不变)——腿1 `GET <生效源>/<pkg>/latest` 版本文档优先(KB 级小载荷、packument 超帽免疫;URL 拼法同 npmLatest 先例;**超时收紧 `Math.min(timeoutMs, 5_000)`**,同 npmLatest 首腿先例——生效源不可达时兜底最坏时延 40s→25s),readme 命中即短路;空/失败落腿2 packument(20s 满额,行为同 0.9.45)。任一腿成功即成功:readme 空渲染「没有 README」(含 latest 空白 readme + packument 失败的降级,不抛);两腿全败抛终末腿(packument)错误。两腿均在生效源(ADR-0012),**不跨源**;两腿 readme 判空同一 trim 规则;repo 随到达的腿取 repository(packument 到达时以 packument 为准)。
|
|
22
|
+
- **未装文案 profile-aware**:`readInstalledPluginReadme` 新增第三参 profileName(缺省 'web'),host-api readme 分支传 `profile.name`——desktop profile 报「desktop profile 未安装该插件: X」(形态对齐 profile-ops 先例);market/toggle/profile-transaction 等 web 事务专用路径的「web profile」文案不变(desktop 走官方 pluginManager 委派,到不了那些代码)。
|
|
23
|
+
- **兜底失败摘要(0.9.45「兜底失败如实抛原始错误」的语义修订)**:两腿全败时抛 `<本地未装错误>(npm README 兜底失败: <终末腿原因>)`——真实死因(如「响应超过上限 8388608 字节」)不再被吞,红字自解释;`cause` 保留原始本地错误。
|
|
24
|
+
- **测试**:versions.test.mjs describe 重写为阶梯用例组(显式 registry 贯穿两腿、缺省生效源形态断言、latest 命中短路、空落 packument、latest 失败兜住、空白 readme 降级、终末腿错误、两腿都空、超时收紧 deps spy、repo 归一语义注记),红→绿全程断言(红集合 {2,3,4,6,8,9} 与计划推演逐条吻合);installed.test.mjs 增 profileName 用例;host-api.test.mjs readme 用例改断言 + 增 profile.name 传递与合成错误形态两用例。全量测试零新增失败(Windows 本机 symlink 族既有基线豁免);typecheck 零错误。
|
|
25
|
+
|
|
11
26
|
### 0.9.49 变更:已装 README 基址贯通 + 链接归一三件(0.9.48 深入分析跟进)
|
|
12
27
|
|
|
13
28
|
- **已装视图基址贯通(P0,主缺口)**:0.9.48 已装详情挂载点误传不存在的 `it.github`,而已装插件走本地读取路径(`repo` 字段只在 npm 兜底路径附带)→ 已装 README 相对链接全部归 `#`。现改传 `vm.githubRepo`——installed-view 视图模型现成的三级兜底(registry 匹配 `registryGithub` → 已装包 package.json `repository` 解析 → 安装 spec `github:o/r` 解析),图标与 LinksRow 早就在用。装机浏览场景的相对链接/图片自此全部可锚定。
|
|
@@ -371,6 +386,21 @@ The full release history of dsh-m, maintained bilingually: **Chinese first, Engl
|
|
|
371
386
|
|
|
372
387
|
## English
|
|
373
388
|
|
|
389
|
+
### Added in 0.9.51 — community entries gain a derived github field (icon coverage 45.6% → 100%)
|
|
390
|
+
|
|
391
|
+
- **Motivation**: community npm-source cards always fell back to the initial letter — the adapter's npm branch only produced the `npm` field and discarded the GitHub repo mapping inside the upstream `url` (demoted to `homepage` only), so `github` stayed unset and the client Icon owner-avatar fallback (`github.com/<owner>.png?size=64`) never fired. Measured against `dsh-plugin-catalog@2026.1007.4837`: 2,298 of 2,298 npm-source entries among 4,226 adaptable ones (**100%**) carry a github.com url — pure data discarding, not an upstream gap.
|
|
392
|
+
- **Change (single point)**: the adapter npm branch now derives `github = githubFromUrl(url)` (`/tree/` subpaths resolve to the repo root; original casing preserved; derivation failure produces no key and never skips the entry — lenient entry semantics, Q43). `source` semantics unchanged; zero client changes — Icon fallback, the details row gaining "GitHub · npm", and installed-view `registryGithub` (first tier of the README base-URL fallback) all light up through existing pipelines.
|
|
393
|
+
- **Activated semantics (accepted on record)**: ① the merge layer's github-collision branch now applies to community npm entries (same-repo entries yield to curated, curated always wins) — full real-catalog simulation shows zero new displacements (4,215 → 4,215); ② plugins installed from GitHub source can now match community npm entries, upgrading via the npm source (same semantics curated dual-source entries already had). Zero performance regression: no new host-side network requests, latest-probe path and cache keys unchanged, the Q46 community-github probe exemption and GithubBudget untouched.
|
|
394
|
+
- **Tests**: adapter flips 1 assertion + nets 4 cases (github url derivation with original casing, non-github host, missing url, invalid shape), plus 1 added assertion in the existing subpath case (repo root); merge layer nets 2 cases (github-collision displacement, GitHub-source installed matching; both red → green); full suite shows zero new failures against the captured baseline and zero typecheck errors.
|
|
395
|
+
|
|
396
|
+
### Added in 0.9.50 — two-leg /latest README fallback ladder + profile-aware not-installed copy + fallback failure summary
|
|
397
|
+
|
|
398
|
+
- **Motivation (verified on the Shanghai Windows desktop)**: on the desktop profile (effective registry npmmirror), the README fold for billion-context (705k weekly downloads) always failed with "web profile 未安装该插件" — npmmirror's packument measures 8,586,604 bytes, above dsh-m's 8MB (8,388,608) maxBytes cap (readCapped throws a 502 "response over limit"), so the fallback leg always failed and the original local error was rethrown per the 0.9.45 contract; npmjs's packument (8,195,089 bytes) merely happens to sit under the cap with an empty readme field — the web profile's "(no README)" was luck, and this fast-releasing package will outgrow the cap on npmjs too.
|
|
399
|
+
- **Two-leg fallback ladder**: `npmPackumentReadme` now tries `/latest` internally (public signature and `{readme, repo}` shape unchanged) — leg 1 fetches the KB-sized version document (immune to oversized packuments; URL shape per the npmLatest precedent; **timeout tightened to `Math.min(timeoutMs, 5_000)`** like npmLatest's first leg — worst-case fallback latency drops from 40s to 25s) and short-circuits on a non-empty readme; empty/failed falls through to the packument leg (full 20s, unchanged 0.9.45 behavior). Any successful leg wins: an empty readme renders "no README" (including the degraded blank-latest + failed-packument case, no throw); both legs failing throws the terminal (packument) error. Both legs hit the effective registry (ADR-0012), never cross-source; both legs share the same trim-based emptiness rule; repo follows whichever leg was reached (packument wins when it responds).
|
|
400
|
+
- **Profile-aware not-installed copy**: `readInstalledPluginReadme` gains a third parameter profileName (default 'web'), and the host-api readme branch passes `profile.name` — the desktop profile now reports "desktop profile 未安装该插件: X" (shape aligned with the profile-ops precedent); the "web profile" copy in web-transaction-only paths (market/toggle/profile-transaction) is intentionally unchanged (desktop delegates to the official pluginManager and never reaches them).
|
|
401
|
+
- **Fallback failure summary (semantic revision of 0.9.45's "rethrow the original local error")**: when both legs fail, the error becomes `<local error>(npm README 兜底失败: <terminal leg reason>)` — the real cause (e.g. the over-cap 502) is no longer swallowed; the original local error is preserved as `cause`.
|
|
402
|
+
- **Testing**: versions.test.mjs describe rewritten as a ladder suite (explicit registry across both legs, default-registry shape assertions, latest short-circuit, empty-readme fallthrough, latest-failure recovery, blank-readme degradation, terminal-leg error, both-empty, tightened-timeout deps spy, repo-normalization note), asserted red→green throughout (red set {2,3,4,6,8,9} matched the plan case by case); installed.test.mjs adds a profileName case; host-api.test.mjs extends the readme case and adds profile.name pass-through plus composed-error shape cases. Full suite shows zero new failures (existing Windows symlink-family baseline exempt); typecheck clean.
|
|
403
|
+
|
|
374
404
|
### Added in 0.9.49 — installed-view repo base + three link normalizations (follow-up to the 0.9.48 deep-dive)
|
|
375
405
|
|
|
376
406
|
- **Installed-view base wired through (P0, the main gap)**: the 0.9.48 installed-detail mount passed a non-existent `it.github`, and installed plugins take the local-read path (the `repo` field only rides on the npm fallback) → every relative link in installed READMEs collapsed to `#`. Now passes `vm.githubRepo` — the ready three-level fallback in the installed view model (registry match `registryGithub` → installed package.json `repository` parse → install spec `github:o/r` parse), which the icon and LinksRow already consumed. Relative links and images now anchor in the installed-browsing scenario too.
|
package/docs/DESIGN.md
CHANGED
|
@@ -71,7 +71,7 @@
|
|
|
71
71
|
|
|
72
72
|
- **双层模型**:主清单(§2.1–2.4 全部语义不变)之上叠加只读的**社区清单**——awesome-dsh-plugin 维护的全量社区目录(4,377+ 条,日增约 62)。**合并市场 = 主清单 ∪ 社区清单去重(主清单恒优先)**,GUI / agent 工具 / CLI 三端同源同语义。本节修订 §9.1 Q25「不做运行时多源合并」,架构决策见 [ADR-0003](./adr/0003-community-catalog-merge.md)。
|
|
73
73
|
- **数据锚定 npm 包(Q39)**:`dsh-plugin-catalog`(CC0-1.0,版本 `YYYY.MDD.RUN`)为唯一数据锚。获取链:`registry.npmjs.org` dist-tags 探测最新版本(几 KB)→ **版本未变不重拉正文(版本号即 revalidate 验证器)**→ jsDelivr 按精确版本直取 `plugins.json`(版本 pin 后内容不可变,CDN 缓存无害)→ npmmirror files API → unpkg 按精确版本直取(末位兜底;均为纯 JSON 文件线路,不引入 tar 解包依赖)。npm 精确版本不可变 = 完整性锚;**不做包内快照兜底**(对日增 62 条的目录,过期数据不得冒充最新——陈旧必须显式标注),失败如实报错(发生了什么 / 为什么 / 现在怎么办)。
|
|
74
|
-
- **目录适配层**:原生条目 → 收录条目。`description.zh`(缺省回退 `en`;>500 字符截断并计数)→ description;`npm` 非空 → `source: npm
|
|
74
|
+
- **目录适配层**:原生条目 → 收录条目。`description.zh`(缺省回退 `en`;>500 字符截断并计数)→ description;`npm` 非空 → `source: npm`(`url` 为 github.com 形态时同时派生 `github` owner/repo,子包取 repo 根,0.9.51 起),否则 `source: github`(owner/repo 取自 url);`url` → homepage;tags 置空;**分类保留原生值不转译**(开放集,Q40);`stars/downloads/capabilities/screenshots` 等不进收录条目本体,仅在市场层作为旁路数据透传展示。id 合成:原生 `name` 是裸名(实测 4,377 条中 190 个重名),合成小写 `owner--name` 形态并满足 v1 id 规则(非法字符折叠为 `-`,超 64 字符截断加哈希尾缀),适配层内冲突追加序号。
|
|
75
75
|
- **校验语义(Q43)**:社区清单独立校验——容器层严格(顶层键白名单、`plugins` 数组、32 MiB / 30,000 条上限,超限**整份拒收**)+ 条目层宽松(单条不合格**跳过 + 计数**,汇入 warnings,不整份拒绝)。主清单严格校验(§2.2)不变。**无 npm 的 monorepo 子包条目跳过并计数**(repo 根不是正确安装目标);有 npm 的子包条目按 npm 安装、正常收录;tarball-only 条目按 github 源收录(dsh-m 不安装 tarball)。
|
|
76
76
|
- **分类(Q40)**:精选分类(5 个,主清单 schema 不变)∪ 社区分类(开放集)。已知 20 个社区分类带中文标签进筛选栏「社区」组;`ui`/`tools`/`market` 三 id 与精选同名、共享过滤桶;上游新增的未知分类原样渲染进「社区·新分类」临时组,发版收录标签。
|
|
77
77
|
- **降级语义(Q42)**:主清单 unavailable + 社区可用 → 显示社区条目 + 顶部错误横幅 + 安装不禁用;社区 unavailable → 主清单照常 + 静默 notice;探测失败时回落 `<ns>/awesome/` **运行时缓存**并显式标注 stale(绝不冒充 ready)——运行时缓存语义与主清单自定义源一致,被禁止的只是**包内快照**;社区**绝不**回退主清单伪装。(**0.9.15 修订**:SWR 常态化后市场页不再显示「缓存快照」临时横幅——stale 标注移至设置页社区卡,「绝不冒充 ready / 过期可被观察到」的诚实约束不变。)
|
|
@@ -180,9 +180,9 @@ npm 安装 / GitHub 安装 / 升级 / 自升级 / 卸载是**同一个事务模
|
|
|
180
180
|
3. **设置页**:registry 地址草稿 +「校验并应用」(先校验候选再写 settings,失败不落盘)/「恢复默认」/「下载默认 registry.json」(不改当前配置)/「检查条目可达性」(probe 统计 + 最多 100 条 issue,只读不改配置);同时展示配置地址、当前生效配置、生效来源与 configStatus(含 rejected/回滚原因与维护性 warnings);完整本地路径仅在此页显示。registry 配置为 **live 生效,不出现安装类重启横幅**;安装/卸载/升级仍保留重启横幅。
|
|
181
181
|
|
|
182
182
|
技术:`src/client.js` 经 **esbuild** 打包为 `lib/client.js`;`window.__ModuleLoader__.load({ id: "dsh-m", factory })` 注册;**React 从 module loader require**(零额外运行时依赖);manifest 注入 `@deepseek-ai/dsh-client-runtime`、`@deepseek-ai/dsh-client-ui-slots`、`@deepseek-ai/dsh-client-ui-settings`。
|
|
183
|
-
|
|
183
|
+
图标:条目携带 `github`(github 源,或 npm 源自 url 派生)时自动用 `https://github.com/<owner>.png?size=64`;`icon` 字段可覆盖;两者皆缺首字母色块回退。
|
|
184
184
|
|
|
185
|
-
**README 预览渲染(`src/client/markdown.js`,0.9.47 起)**:自研轻量渲染器,安全模型 = 「白名单标签 + 白名单属性 + safeUrl 闸门 → 全部经 `h()` 构造 React 元素」,**绝不注入原始 HTML 字符串**(无 `dangerouslySetInnerHTML`,文本节点 React 转义兜底)。markdown 侧支持标题/列表/表格/围栏/引用/徽章链接等;HTML 侧支持 GitHub 风 README 常见内嵌子集——块级包裹标签(`p/div/center/details/table/tr/td/li/ul/ol/h1-6` 等)剥壳后渲染:内容纯行内(无空行分段/无 markdown 块结构/行首均为行内标签)时整块行走内 token 流(徽章 + ` ` 连排一行,GitHub 语义,0.9.48),否则按 markdown 递归(保内部结构);行内标签(`a/img/strong/em/code/kbd/mark/br/sub/sup` 等)token 化渲染,`align` 属性映射 `textAlign`、`<img width/height>` 映射显式尺寸(放开徽章 20px 高度帽);未知标签剥壳留文本,`script/style/svg/iframe` 等容器连同内容丢弃,`javascript:` 等危险 URL 归 `#`。**HTML 实体解码(0.9.48)**:命名(60+ 高频集)+ 数字(十/十六进制),文本段与属性值都解、`code/pre` 不解、单趟不回炉、解码结果仅作文本节点。**仓库基址锚定(0.9.48)**:`renderMarkdown(src, { repo: "owner/repo" })` 后相对链接锚 `github.com/<o>/<r>/blob/HEAD/`、相对图片锚 `raw.githubusercontent.com/<o>/<r>/HEAD/`;基址来源 = npm packument `repository`(host-api readme 结果 `repo` 字段,仅 GitHub 归一)> 卡片 `github` 字段;无基址时相对路径归 `#`(0.9.47 前行为)。**链接归一(0.9.49)**:协议相对 `//host/x` 补 `https:`;`git@github.com:o/r(.git)` 内联归一 https 形态;有基址时 `#anchor` 锚 `github.com/<o>/<r>#anchor`(裸 `#` 与 img kind 不锚);已装视图挂载点改传 `vm.githubRepo`(installed-view 三级兜底:registryGithub → 已装包 repository 解析 → spec 解析;0.9.48 曾误传不存在的 `it.github` 致已装 README 相对链接全归 `#`);host 侧 `githubRepoFromRepository` 尾部放宽到 `[/?#]`(与 versions.ts `extractGithubRepo` 同口径)。已知边界:monorepo 子目录 README 的相对路径锚到仓库根(packument 不携带子目录信息);绝对 URL 不受锚定影响。数据源:已装条目读 profile 本地 `README.md`(≤64KB 截断);未装条目回源 npm packument 顶层 `readme
|
|
185
|
+
**README 预览渲染(`src/client/markdown.js`,0.9.47 起)**:自研轻量渲染器,安全模型 = 「白名单标签 + 白名单属性 + safeUrl 闸门 → 全部经 `h()` 构造 React 元素」,**绝不注入原始 HTML 字符串**(无 `dangerouslySetInnerHTML`,文本节点 React 转义兜底)。markdown 侧支持标题/列表/表格/围栏/引用/徽章链接等;HTML 侧支持 GitHub 风 README 常见内嵌子集——块级包裹标签(`p/div/center/details/table/tr/td/li/ul/ol/h1-6` 等)剥壳后渲染:内容纯行内(无空行分段/无 markdown 块结构/行首均为行内标签)时整块行走内 token 流(徽章 + ` ` 连排一行,GitHub 语义,0.9.48),否则按 markdown 递归(保内部结构);行内标签(`a/img/strong/em/code/kbd/mark/br/sub/sup` 等)token 化渲染,`align` 属性映射 `textAlign`、`<img width/height>` 映射显式尺寸(放开徽章 20px 高度帽);未知标签剥壳留文本,`script/style/svg/iframe` 等容器连同内容丢弃,`javascript:` 等危险 URL 归 `#`。**HTML 实体解码(0.9.48)**:命名(60+ 高频集)+ 数字(十/十六进制),文本段与属性值都解、`code/pre` 不解、单趟不回炉、解码结果仅作文本节点。**仓库基址锚定(0.9.48)**:`renderMarkdown(src, { repo: "owner/repo" })` 后相对链接锚 `github.com/<o>/<r>/blob/HEAD/`、相对图片锚 `raw.githubusercontent.com/<o>/<r>/HEAD/`;基址来源 = npm packument/latest 文档 `repository`(host-api readme 结果 `repo` 字段,仅 GitHub 归一)> 卡片 `github` 字段;无基址时相对路径归 `#`(0.9.47 前行为)。**链接归一(0.9.49)**:协议相对 `//host/x` 补 `https:`;`git@github.com:o/r(.git)` 内联归一 https 形态;有基址时 `#anchor` 锚 `github.com/<o>/<r>#anchor`(裸 `#` 与 img kind 不锚);已装视图挂载点改传 `vm.githubRepo`(installed-view 三级兜底:registryGithub → 已装包 repository 解析 → spec 解析;0.9.48 曾误传不存在的 `it.github` 致已装 README 相对链接全归 `#`);host 侧 `githubRepoFromRepository` 尾部放宽到 `[/?#]`(与 versions.ts `extractGithubRepo` 同口径)。已知边界:monorepo 子目录 README 的相对路径锚到仓库根(packument 不携带子目录信息);绝对 URL 不受锚定影响。数据源:已装条目读 profile 本地 `README.md`(≤64KB 截断);未装条目回源 npm,两腿阶梯(0.9.50)——`/latest` 版本文档 `readme` 优先(KB 级小载荷,packument 超帽免疫,超时收紧 ≤5s),空/失败落 packument 顶层 `readme`(0.9.45 U10b);任一腿成功即成功渲染(readme 空 → 「没有 README」),两腿全败抛本地未装错误并附兜底失败摘要(0.9.50);`repo` 随到达的腿取 `repository`(packument 到达时以 packument 为准)。两腿均在生效源(ADR-0012),不跨源。
|
|
186
186
|
|
|
187
187
|
## 5. Agent 工具(host)与 CLI
|
|
188
188
|
|
|
@@ -140,6 +140,10 @@ export function adaptCommunityCatalog(catalog) {
|
|
|
140
140
|
if (npm !== null) {
|
|
141
141
|
source = 'npm';
|
|
142
142
|
npmName = npm;
|
|
143
|
+
// 0.9.51:npm 条目补派生 github(url 为 github.com 形态时取 owner/repo,子包取 repo 根,
|
|
144
|
+
// 原样大小写)——图标 owner 头像兜底 / 详情 GitHub 链接 / 已装匹配与精选双源条目同语义。
|
|
145
|
+
// 派生失败不产出键、不跳过条目(条目层宽松,Q43)。
|
|
146
|
+
github = githubFromUrl(raw.url) ?? undefined;
|
|
143
147
|
}
|
|
144
148
|
else {
|
|
145
149
|
const gh = githubFromUrl(raw.url);
|
package/lib/core/host-api.js
CHANGED
|
@@ -440,19 +440,22 @@ export function createApiDispatcher(ctx) {
|
|
|
440
440
|
throw new ApiProtocolError(400, '缺少 pkg');
|
|
441
441
|
// 0.9.0 双 profile:README 优先只读当前 profile 的 node_modules;
|
|
442
442
|
// 0.9.45 U10b 修复:市场/收藏详情 Modal 的 README 折叠页对「未安装」条目也会发起读取
|
|
443
|
-
// (装前决策场景)——本地未装时回源 npm
|
|
444
|
-
//
|
|
443
|
+
// (装前决策场景)——本地未装时回源 npm 兜底(0.9.50 起为 /latest + packument 两腿阶梯);
|
|
444
|
+
// 0.9.50 语义修订:兜底两腿全败不再只抛原始本地错误——本地错误为主语、附「npm README
|
|
445
|
+
// 兜底失败: <终末腿原因>」摘要,真实死因(如响应超帽)不再被吞。
|
|
445
446
|
let result;
|
|
446
447
|
try {
|
|
447
|
-
result = await d.readInstalledPluginReadme(target, profile.dir);
|
|
448
|
+
result = await d.readInstalledPluginReadme(target, profile.dir, profile.name);
|
|
448
449
|
}
|
|
449
450
|
catch (localErr) {
|
|
450
451
|
try {
|
|
451
452
|
const fallback = await d.npmPackumentReadme(target);
|
|
452
453
|
result = { pkg: target, name: target, readme: fallback.readme, truncated: false, repo: fallback.repo || undefined };
|
|
453
454
|
}
|
|
454
|
-
catch {
|
|
455
|
-
|
|
455
|
+
catch (fallbackErr) {
|
|
456
|
+
const localMsg = localErr instanceof Error ? localErr.message : String(localErr);
|
|
457
|
+
const fallbackMsg = fallbackErr instanceof Error ? fallbackErr.message : String(fallbackErr);
|
|
458
|
+
throw new Error(`${localMsg}(npm README 兜底失败: ${fallbackMsg})`, { cause: localErr });
|
|
456
459
|
}
|
|
457
460
|
}
|
|
458
461
|
payload = { ...result };
|
package/lib/core/installed.js
CHANGED
|
@@ -222,8 +222,8 @@ async function readTextLimited(path, limit) {
|
|
|
222
222
|
await fh.close().catch(() => undefined);
|
|
223
223
|
}
|
|
224
224
|
}
|
|
225
|
-
/** 读取单个已安装插件的 README(UTF-8,≤64KB,超限截断)。pkg 必须来自 profile 依赖。 */
|
|
226
|
-
export async function readInstalledPluginReadme(pkg, profileDir = webProfileDir()) {
|
|
225
|
+
/** 读取单个已安装插件的 README(UTF-8,≤64KB,超限截断)。pkg 必须来自 profile 依赖。profileName 仅用于错误文案(0.9.50:desktop 等 profile 不再误报「web profile」,形态对齐 profile-ops 先例)。 */
|
|
226
|
+
export async function readInstalledPluginReadme(pkg, profileDir = webProfileDir(), profileName = 'web') {
|
|
227
227
|
const key = String(pkg || '').trim();
|
|
228
228
|
if (!isSafePkgName(key))
|
|
229
229
|
throw new Error(`无效插件包名: ${pkg}`);
|
|
@@ -232,7 +232,7 @@ export async function readInstalledPluginReadme(pkg, profileDir = webProfileDir(
|
|
|
232
232
|
// 授权边界必须用 own-property 判定:`in` 会沿原型链命中继承属性(如 'constructor'),
|
|
233
233
|
// 绕过「pkg 必须来自 profile dependencies」的成员约束
|
|
234
234
|
if (!Object.hasOwn(deps, key)) {
|
|
235
|
-
throw new Error(
|
|
235
|
+
throw new Error(`${profileName} profile 未安装该插件: ${key}`);
|
|
236
236
|
}
|
|
237
237
|
const dir = resolvePluginDir(root, key, deps[key]);
|
|
238
238
|
if (!dir)
|
package/lib/core/versions.js
CHANGED
|
@@ -130,24 +130,49 @@ export function extractGithubRepo(v) {
|
|
|
130
130
|
}
|
|
131
131
|
/**
|
|
132
132
|
* npm packument readme 兜底(0.9.45 U10b 修复):市场/收藏详情 Modal 的 README 折叠页对
|
|
133
|
-
* 「未安装」条目无法走本地 profile 读取——回源
|
|
134
|
-
*
|
|
135
|
-
*
|
|
136
|
-
* 0.9.48:随附 repo(
|
|
137
|
-
*
|
|
133
|
+
* 「未安装」条目无法走本地 profile 读取——回源 npm 兜底。安全基线同 npmPackument:HTTPS、
|
|
134
|
+
* 8MB 上限;无 readme 字段返回空串(不抛,客户端显示「没有 README」);L1(ADR-0012):
|
|
135
|
+
* 显式 registry 参数 > 生效源。
|
|
136
|
+
* 0.9.48:随附 repo(repository 归一为 owner/repo,仅 GitHub),供 README 相对链接/图片
|
|
137
|
+
* 锚定仓库基址;无 repository 或非 GitHub 时为空串。
|
|
138
|
+
* 0.9.50:两腿阶梯——腿1 `/latest` 版本文档优先(KB 级小载荷,超大 packument 超帽免疫;
|
|
139
|
+
* 超时收紧 `Math.min(timeoutMs, 5_000)`,同 npmLatest 首腿先例),readme 命中即短路;空或
|
|
140
|
+
* 腿失败落腿2 packument(20s 满额,行为同 0.9.45)。任一腿成功即成功——readme 空渲染
|
|
141
|
+
* 「没有 README」(含 latest 空白 readme + packument 失败的降级,不抛);两腿全败抛终末腿
|
|
142
|
+
* (packument)错误。两腿均在生效源,不跨源;两腿 readme 判空同一 trim 规则。repo 随到达
|
|
143
|
+
* 的腿取 repository(packument 到达时以 packument 为准)。
|
|
138
144
|
*/
|
|
139
145
|
export async function npmPackumentReadme(pkg, timeoutMs = 20_000, signal, registry, deps) {
|
|
140
146
|
if (!/^@?[A-Za-z0-9-._~]+(\/[A-Za-z0-9-._~]+)?$/.test(pkg))
|
|
141
147
|
throw new Error(`无效 npm 包名: ${pkg}`);
|
|
142
148
|
const fetcher = deps?.fetchJsonLimited ?? fetchJsonLimited;
|
|
143
149
|
const base = registry?.trim() ? registry.trim() : await activeNpmRegistry();
|
|
144
|
-
const
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
150
|
+
const extractReadme = (data) => typeof data?.readme === 'string' && data.readme.trim() !== '' ? data.readme : '';
|
|
151
|
+
// 腿1:/latest 版本文档(小载荷;超时收紧同 npmLatest 首腿先例)
|
|
152
|
+
let latestOk = false;
|
|
153
|
+
let latestRepo = '';
|
|
154
|
+
try {
|
|
155
|
+
const latestDoc = await fetcher(`${registryBase(base)}/${encodeURIComponent(pkg)}/latest`, { timeoutMs: Math.min(timeoutMs, 5_000), signal, maxBytes: 8 * 1024 * 1024 });
|
|
156
|
+
latestOk = true;
|
|
157
|
+
latestRepo = extractGithubRepo(latestDoc?.repository);
|
|
158
|
+
const latestReadme = extractReadme(latestDoc);
|
|
159
|
+
if (latestReadme)
|
|
160
|
+
return { readme: latestReadme, repo: latestRepo };
|
|
161
|
+
}
|
|
162
|
+
catch {
|
|
163
|
+
// latest 腿失败 → 落 packument 腿(终末腿语义见下)
|
|
164
|
+
}
|
|
165
|
+
// 腿2:packument(0.9.45 U10b 原兜底腿,行为不变)
|
|
166
|
+
try {
|
|
167
|
+
const data = await fetcher(`${registryBase(base)}/${encodeURIComponent(pkg)}`, { timeoutMs, signal, maxBytes: 8 * 1024 * 1024 });
|
|
168
|
+
return { readme: extractReadme(data), repo: extractGithubRepo(data?.repository) };
|
|
169
|
+
}
|
|
170
|
+
catch (packumentErr) {
|
|
171
|
+
// latest 腿已成功证明包在而无有效 readme → 降级成功(空 readme),不冒充失败
|
|
172
|
+
if (latestOk)
|
|
173
|
+
return { readme: '', repo: latestRepo };
|
|
174
|
+
throw packumentErr;
|
|
175
|
+
}
|
|
151
176
|
}
|
|
152
177
|
/**
|
|
153
178
|
* 升级探测(检测读)。L3 权威链(ADR-0012):新鲜度敏感——npmjs 权威源优先
|