@morehao/dsh-github-trending 0.3.0 → 0.3.2

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
@@ -1,5 +1,26 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.3.2 - 2026-10-31
4
+
5
+ ### 改进
6
+
7
+ - **更新入口更显眼**:概览页新增「插件更新」卡片——当前 / 最新版本、检查更新、更新到 vX、本地安装时的手动命令都在这里;侧栏包名在有新版时显示红点。关于弹层与概览共用同一个 controller(模块级单例),打开多个位置不会重复请求。
8
+ - 关于弹层的更新区块改为复用同一组件(`client/update-panel.ts`),行为不变。
9
+
10
+ ## 0.3.1 - 2026-10-31
11
+
12
+ ### 新增
13
+
14
+ - **插件自更新**:关于弹层显示当前版本与 npm 最新版,打开时静默检查(Host 侧 5 分钟缓存),也可手动「检查更新」(`GET /update?fresh=1`)。确认后经宿主 `pluginManager` 重装本插件:`installBundle` 返回 `already-installed` 时走 `removeBundle → installBundle`,remove 之后安装失败会尝试装回原版本,成功后提示重启 DSH 生效。`link:` / `file:` / `workspace:` 安装不提供一键更新,只给 `dsh plugin --profile <name> add ...@latest` 供复制;没有 `pluginManager` 时同样退化为手动命令。
15
+ - **共享引擎 `@morehao/dsh-plugin-kit`**:更新引擎(查版本 / semver / registry 计划 / 安装路径 / 事件)与客户端状态机放进共享包,本仓新增 `src/host/update.ts` 只做 `ctx.get` 与 `execCapture` 接线;`POST /update` 的业务失败返回 409 + `UPDATE_FAILED` + `updateCode`,不占 500。
16
+ - 客户端构建守卫允许依赖包源码进入 bundle(`link:` / `file:` 依赖会解析到仓库外),本仓 `src/`、`lib/` 仍禁止进客户端产物。
17
+
18
+ ### 修复
19
+
20
+ - **关于弹层拿不到主题变量**:`AboutDialog` 挂在 `.dshgt-root` 之外,`--gt-*` 没定义,弹层背景/边框退化成透明、文字对比度极低。现在弹层外面包一层 `.dshgt-root` 再进 mask。
21
+ - **日报里的表格不再被拍平成一个段落**:`client/markdown-core.ts` 补上 GFM 管道表解析——表头 + 分隔行(`---` / `:---` / `:---:` / `---:` 决定列对齐)+ 数据行,单元格按行内规则解析(链接、粗体、行内代码),支持反引号里的 `|` 与 `\|` 转义;表头/分隔行列数不一致或缺少分隔行时退回段落,不丢内容。`client/markdown.ts` 渲染 `table` / `thead` / `tbody` / `th` / `td` 并按对齐写 `text-align`;此前整张表会被当成「一行满是 `|` 的文字」。
22
+ - 表格样式随附:表头浅底、行分隔线、偶数行斑马纹、窄容器横向滚动;单元格内 `word-break: normal`,避免 `185,423` 这类数字被拆成两行。
23
+
3
24
  ## 0.3.0 - 2026-10-31
4
25
 
5
26
  ### 变更(含破坏性)
package/README.md CHANGED
@@ -37,11 +37,18 @@ dsh plugin --profile <profile> add @morehao/dsh-github-trending
37
37
 
38
38
  - 运行成功后,Host 从核心本次返回的 `outcome.files` 里挑当天的 `.md`(优先 `YYYY-MM-DD.md`,否则只有一个 `.md` 时用它),**只读**复制到 `$DSH_HOME/github-trending/previews/<runId>.md`:路径必须在 `repo.path` 内(词法 + realpath 各判一次)、不超过 512 KB、sha256 与契约声明一致;失败不阻断运行,只是这次没有预览。
39
39
  - 预览副本 0600、临时文件 + rename、最多保留 30 份;`state.json` / `history.json` 只存 `file` / `bytes` / `sha256` 元数据,不存正文。
40
- - `GET /preview?id=<runId>` 只接受历史里出现过的 id、不接路径参数;返回 `{ text, truncated }`。客户端渲染 Markdown 子集(标题 / 段落 / 列表 / 引用 / 粗体 / 行内代码 / 链接 / 分隔线),**只生成 React 文本节点**,链接仅放行 http(s),不渲染图片或原始 HTML;也可以切「原文」。
40
+ - `GET /preview?id=<runId>` 只接受历史里出现过的 id、不接路径参数;返回 `{ text, truncated }`。客户端渲染 Markdown 子集(标题 / 段落 / 列表 / 引用 / 代码块 / 表格(GFM 管道表,含列对齐)/ 粗体 / 行内代码 / 链接 / 分隔线),**只生成 React 文本节点**,链接仅放行 http(s),不渲染图片或原始 HTML;也可以切「原文」。
41
41
  - 日报只列最近 30 天**有记录**的日期,每天一条;默认打开最近一期有预览的日报。失败 / 无变更 / 试跑 / 旧记录 / 副本被清理都有各自的空状态,不用示例内容兜底。
42
42
  - 预览是运行结束时的派生副本:你在仓库里事后编辑 canonical 文件,不会改变这次记录。
43
43
  - 关于入口在左轨底部:包名 / 版本 / GitHub 来自 `/state.plugin`(`repository` 由 `package.json` 规范化),仓库缺失时隐藏外链。
44
44
 
45
+ ## 插件更新
46
+
47
+ - 关于弹层里会显示当前版本与 npm 上的最新版:打开时静默检查一次(Host 侧缓存 5 分钟),也可以点「检查更新」强制重查。只跟随 npm 的 `latest` 稳定版。
48
+ - 确认后由宿主 `pluginManager` 重装本插件(`installBundle`;已装同 spec 时走 `removeBundle → installBundle`,失败会尝试装回原版本),完成后需要重启 DSH 生效。
49
+ - 本地安装(`link:` / `file:` / `workspace:`)**不提供一键更新**:那会把开发链接换成 npm 版。此时只显示 `dsh plugin --profile <name> add @morehao/dsh-github-trending@latest` 供复制。
50
+ - 更新引擎来自共享包 [`@morehao/dsh-plugin-kit`](https://www.npmjs.com/package/@morehao/dsh-plugin-kit)(`./update` + `./update/client`),本仓只做上下文与 `execCapture` 接线;`GET /update` / `POST /update` 与其它路由一样要求自定义头、`no-store`,业务失败返回 409 + `UPDATE_FAILED` + `updateCode`。
51
+
45
52
  ## 它需要什么 / 它访问哪里 / 它不上传什么
46
53
 
47
54
  **需要什么**
package/client.js CHANGED
@@ -339,7 +339,26 @@ var DICT = {
339
339
  "about.license": "许可",
340
340
  "about.core": "核心",
341
341
  "about.env": "运行环境",
342
- "about.env.value": "DSH Web · Node 插件"
342
+ "about.env.value": "DSH Web · Node 插件",
343
+ "about.update.title": "插件更新",
344
+ "about.update.check": "检查更新",
345
+ "about.update.checking": "正在检查最新版…",
346
+ "about.update.current": "已是最新(v{version})",
347
+ "about.update.available": "有新版本 v{latest}(当前 v{current})",
348
+ "about.update.apply": "更新到 v{version}",
349
+ "about.update.confirm": "确认更新到 v{version}?会重装本插件并重载面板,之后需要重启 DSH 生效。",
350
+ "about.update.confirmYes": "确认更新",
351
+ "about.update.cancel": "取消",
352
+ "about.update.applying": "正在更新到 v{version}…",
353
+ "about.update.done": "已更新到 v{version},重启 DSH 后生效。",
354
+ "about.update.applied": "已更新到 v{version}。",
355
+ "about.update.failed": "更新失败:{message}",
356
+ "about.update.checkFailed": "检查更新失败:拿不到 registry 上的最新版。可以稍后重试,或手动执行下面的命令。",
357
+ "about.update.manual": "当前是本地 {kind} 安装,不能一键更新(会把开发链接换成 npm 版)。手动更新:",
358
+ "about.update.copy": "复制命令",
359
+ "about.update.copied": "已复制",
360
+ "about.update.hint": "只检查 npm 上的 latest 稳定版;更新后需要重启 DSH。",
361
+ "update.badge": "有新版本可用"
343
362
  },
344
363
  en: {
345
364
  "section.title": "GitHub Trending",
@@ -635,7 +654,26 @@ var DICT = {
635
654
  "about.license": "License",
636
655
  "about.core": "Core",
637
656
  "about.env": "Runtime",
638
- "about.env.value": "DSH Web · Node plugin"
657
+ "about.env.value": "DSH Web · Node plugin",
658
+ "about.update.title": "Plugin update",
659
+ "about.update.check": "Check for updates",
660
+ "about.update.checking": "Checking the latest version…",
661
+ "about.update.current": "Up to date (v{version})",
662
+ "about.update.available": "v{latest} is available (current v{current})",
663
+ "about.update.apply": "Update to v{version}",
664
+ "about.update.confirm": "Update to v{version}? The plugin will be reinstalled and the panel reloaded; restart DSH afterwards.",
665
+ "about.update.confirmYes": "Update now",
666
+ "about.update.cancel": "Cancel",
667
+ "about.update.applying": "Updating to v{version}…",
668
+ "about.update.done": "Updated to v{version}; restart DSH to apply.",
669
+ "about.update.applied": "Updated to v{version}.",
670
+ "about.update.failed": "Update failed: {message}",
671
+ "about.update.checkFailed": "Update check failed: cannot reach the registry for the latest version. Retry later or run the command below.",
672
+ "about.update.manual": "Installed from a local {kind} spec; one-click update is disabled (it would replace the dev link). Update manually:",
673
+ "about.update.copy": "Copy command",
674
+ "about.update.copied": "Copied",
675
+ "about.update.hint": "Checks the npm latest stable tag only; applying requires a DSH restart.",
676
+ "update.badge": "Update available"
639
677
  }
640
678
  };
641
679
  function activeLang(locale) {
@@ -661,11 +699,11 @@ var CSS = `
661
699
  .dshgt-root{--gt-fg:var(--dsw-alias-label-primary,#1f2328);--gt-muted:var(--dsw-alias-label-secondary,#6b7280);--gt-bg:var(--dsw-alias-bg-base,#fff);--gt-card:var(--dsw-alias-bg-layer-1,#f8fafc);--gt-border:var(--dsw-alias-border-l2,#e5e7eb);--gt-accent:var(--dsw-alias-state-business-primary,#2563eb);--gt-ok:var(--dsw-alias-state-success-primary,#16a34a);--gt-warn:var(--dsw-alias-state-warn-primary,#d97706);--gt-err:var(--dsw-alias-state-error-primary,#dc2626);color:var(--gt-fg);font-size:14px;line-height:1.55;display:flex;flex-direction:column;gap:12px;}
662
700
  .dshgt-root *{box-sizing:border-box;}
663
701
  .dshgt-head{display:flex;align-items:baseline;gap:10px;flex-wrap:wrap;}
664
- .dshgt-title{font-size:15px;font-weight:600;}
702
+ .dshgt-title{font-size:15px;font-weight:600;white-space:nowrap;}
665
703
  .dshgt-sub{color:var(--gt-muted);font-size:12px;}
666
704
  .dshgt-tabs{display:flex;gap:6px;border-bottom:1px solid var(--gt-border);}
667
705
  .dshgt-tab{appearance:none;border:none;background:none;padding:6px 10px;font:inherit;color:var(--gt-muted);cursor:pointer;border-bottom:2px solid transparent;}
668
- .dshgt-tab[aria-selected="true"]{color:var(--gt-fg);border-bottom-color:var(--gt-accent);font-weight:600;}
706
+ .dshgt-tab[aria-selected="true"]{color:var(--gt-fg);border-bottom-color:var(--gt-accent);font-weight:600;white-space:nowrap;}
669
707
  .dshgt-shell{height:100%;min-height:0;overflow:hidden;}
670
708
  .dshgt-content{flex:1;min-height:0;display:flex;}
671
709
  .dshgt-scroll{flex:1;min-height:0;overflow-y:auto;display:flex;flex-direction:column;gap:12px;padding-right:4px;}
@@ -673,12 +711,12 @@ var CSS = `
673
711
  .dshgt-vtabs{display:flex;flex-direction:column;gap:2px;border-right:1px solid var(--gt-border);padding-right:8px;overflow-y:auto;}
674
712
  .dshgt-vtab{appearance:none;border:none;background:none;text-align:left;padding:7px 10px;border-radius:7px;color:var(--gt-muted);cursor:pointer;font:inherit;font-size:12px;display:flex;align-items:center;gap:6px;}
675
713
  .dshgt-vtab:hover{background:var(--dsw-alias-interactive-bg-hover,color-mix(in srgb,var(--gt-accent) 8%,transparent));}
676
- .dshgt-vtab[aria-selected="true"]{background:color-mix(in srgb,var(--gt-accent) 12%,transparent);color:var(--gt-fg);font-weight:600;}
714
+ .dshgt-vtab[aria-selected="true"]{background:color-mix(in srgb,var(--gt-accent) 12%,transparent);color:var(--gt-fg);font-weight:600;white-space:nowrap;}
677
715
  .dshgt-vtab-dirty{width:6px;height:6px;border-radius:50%;background:var(--gt-warn);flex:none;margin-left:auto;}
678
716
  .dshgt-vpanel{min-width:0;min-height:0;overflow-y:auto;display:flex;flex-direction:column;gap:12px;padding-right:4px;}
679
717
  .dshgt-card{border:1px solid var(--gt-border);border-radius:10px;background:var(--gt-card);padding:12px 14px;display:flex;flex-direction:column;gap:8px;}
680
718
  .dshgt-card-h{display:flex;align-items:center;gap:8px;flex-wrap:wrap;}
681
- .dshgt-card-h h3{margin:0;font-size:13px;font-weight:600;}
719
+ .dshgt-card-h h3{margin:0;font-size:13px;font-weight:600;white-space:nowrap;}
682
720
  .dshgt-hint{color:var(--gt-muted);font-size:12px;}
683
721
  .dshgt-desc{margin:0;color:var(--gt-muted);font-size:12px;}
684
722
  .dshgt-row{display:flex;gap:10px;align-items:center;min-height:28px;flex-wrap:wrap;}
@@ -736,7 +774,7 @@ var CSS = `
736
774
  .dshgt-nav-item{display:flex;align-items:center;gap:9px;height:36px;padding:0 9px;border:1px solid transparent;border-radius:8px;background:none;text-align:left;font-family:inherit;font-size:13.5px;color:var(--gt-muted);width:100%;cursor:pointer;}
737
775
  .dshgt-nav-item svg{flex:none;opacity:.82;}
738
776
  .dshgt-nav-item:hover{background:color-mix(in srgb,var(--gt-fg) 6%,var(--gt-bg));color:var(--gt-fg);}
739
- .dshgt-nav-item.on{background:var(--gt-bg);border-color:var(--gt-border);color:var(--gt-fg);font-weight:600;}
777
+ .dshgt-nav-item.on{background:var(--gt-bg);border-color:var(--gt-border);color:var(--gt-fg);font-weight:600;white-space:nowrap;}
740
778
  .dshgt-nav-item.on svg{opacity:1;}
741
779
  .dshgt-nav-tail{margin-left:auto;display:flex;align-items:center;gap:5px;font-size:11.5px;color:var(--gt-muted);font-weight:400;}
742
780
  .dshgt-rail-spacer{flex:1;min-height:16px;}
@@ -833,14 +871,14 @@ var CSS = `
833
871
  .dshgt-digest-bar{display:flex;align-items:center;gap:8px;flex-wrap:wrap;padding:9px 12px;border-bottom:1px solid color-mix(in srgb,var(--gt-border) 70%,transparent);background:color-mix(in srgb,var(--gt-fg) 2.5%,var(--gt-card));}
834
872
  .dshgt-seg{display:inline-flex;border:1px solid var(--gt-border);border-radius:8px;padding:2px;background:var(--gt-bg);gap:2px;}
835
873
  .dshgt-seg button{border:0;background:none;height:26px;padding:0 11px;border-radius:6px;font-family:inherit;font-size:12.5px;color:var(--gt-muted);cursor:pointer;}
836
- .dshgt-seg button.on{background:var(--gt-card);color:var(--gt-fg);font-weight:600;}
874
+ .dshgt-seg button.on{background:var(--gt-card);color:var(--gt-fg);font-weight:600;white-space:nowrap;}
837
875
  .dshgt-digest-doc{padding:26px 32px 22px;min-width:0;}
838
876
  .dshgt-doc-meta{display:flex;gap:12px;flex-wrap:wrap;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:11px;color:var(--gt-muted);padding-bottom:12px;border-bottom:1px solid color-mix(in srgb,var(--gt-border) 70%,transparent);}
839
877
  .dshgt-digest-raw{margin:0;padding:22px 28px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12px;line-height:1.75;white-space:pre-wrap;word-break:break-word;color:var(--gt-muted);background:color-mix(in srgb,var(--gt-fg) 2.5%,var(--gt-card));max-height:640px;overflow:auto;}
840
878
  .dshgt-digest-loading{padding:40px 28px;color:var(--gt-muted);font-size:13px;}
841
879
  .dshgt-digest-empty{padding:42px 36px 46px;max-width:720px;}
842
880
  .dshgt-status-line{display:flex;align-items:center;gap:8px;font-size:12.5px;color:var(--gt-muted);}
843
- .dshgt-status-line b{color:var(--gt-fg);font-weight:600;}
881
+ .dshgt-status-line b{color:var(--gt-fg);font-weight:600;white-space:nowrap;}
844
882
  .dshgt-digest-empty h2{margin:14px 0 6px;font-size:20px;font-weight:650;letter-spacing:-.012em;}
845
883
  .dshgt-digest-empty p{margin:0;max-width:580px;font-size:13.5px;line-height:1.7;color:var(--gt-muted);}
846
884
  .dshgt-digest-error{margin-top:10px;color:var(--gt-err);}
@@ -855,6 +893,12 @@ var CSS = `
855
893
  .dshgt-md-list{margin:8px 0;padding-left:22px;}
856
894
  .dshgt-md-list li{margin:3px 0;}
857
895
  .dshgt-md-quote{margin:12px 0;padding:2px 0 2px 14px;border-left:3px solid var(--gt-border);color:var(--gt-muted);}
896
+ .dshgt-md-tablewrap{margin:12px 0;border:1px solid var(--gt-border);border-radius:8px;overflow-x:auto;}
897
+ .dshgt-md-table{width:100%;min-width:560px;border-collapse:collapse;font-size:13px;line-height:1.6;}
898
+ .dshgt-md-table th,.dshgt-md-table td{padding:7px 10px;text-align:left;vertical-align:top;border-bottom:1px solid color-mix(in srgb,var(--gt-border) 65%,transparent);word-break:normal;overflow-wrap:break-word;}
899
+ .dshgt-md-table th{background:color-mix(in srgb,var(--gt-fg) 4%,var(--gt-card));font-weight:600;white-space:nowrap;}
900
+ .dshgt-md-table tbody tr:last-child td{border-bottom:0;}
901
+ .dshgt-md-table tbody tr:nth-child(even){background:color-mix(in srgb,var(--gt-fg) 2%,var(--gt-card));}
858
902
  .dshgt-md-pre{margin:12px 0;padding:12px 14px;border-radius:8px;background:color-mix(in srgb,var(--gt-fg) 5%,var(--gt-card));overflow:auto;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12px;line-height:1.6;}
859
903
  .dshgt-md-code{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12px;background:color-mix(in srgb,var(--gt-fg) 7%,var(--gt-card));padding:1px 4px;border-radius:4px;}
860
904
  .dshgt-md-hr{border:0;border-top:1px solid var(--gt-border);margin:18px 0;}
@@ -899,6 +943,20 @@ var CSS = `
899
943
  .dshgt-about .dshgt-kv{grid-template-columns:110px 1fr;}
900
944
  .dshgt-about .dshgt-kv a{color:var(--gt-accent);text-decoration:none;word-break:break-all;}
901
945
  .dshgt-about .dshgt-kv a:hover{text-decoration:underline;}
946
+ .dshgt-update{margin-top:14px;padding-top:12px;border-top:1px solid color-mix(in srgb,var(--gt-border) 70%,transparent);}
947
+ .dshgt-update-head{display:flex;align-items:baseline;gap:8px;flex-wrap:wrap;}
948
+ .dshgt-update-head h4{margin:0;font-size:13px;font-weight:600;}
949
+ .dshgt-update-head .dshgt-hint{font-size:11.5px;}
950
+ .dshgt-update-line{margin:6px 0 0;font-size:12.5px;color:var(--gt-muted);line-height:1.55;}
951
+ .dshgt-update-err{color:var(--gt-err);}
952
+ .dshgt-update-warn{margin:6px 0 0;font-size:12px;color:var(--gt-warn);line-height:1.55;}
953
+ .dshgt-update-actions{display:flex;gap:8px;flex-wrap:wrap;align-items:center;margin-top:9px;}
954
+ .dshgt-update-manual{margin-top:9px;padding:9px 10px;border:1px solid color-mix(in srgb,var(--gt-warn) 35%,var(--gt-border));border-radius:8px;background:color-mix(in srgb,var(--gt-warn) 6%,var(--gt-card));}
955
+ .dshgt-update-manual .dshgt-update-warn{margin:0 0 6px;}
956
+ .dshgt-update-cmd{display:block;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:11.5px;line-height:1.5;color:var(--gt-fg);word-break:break-all;user-select:all;}
957
+ .dshgt-update-dot{display:inline-block;width:6px;height:6px;margin-left:6px;border-radius:50%;background:var(--gt-err);vertical-align:middle;flex:none;}
958
+ .dshgt-update-bare{margin-top:0;padding-top:0;border-top:0;}
959
+ .dshgt-update-card .dshgt-card-h{margin-bottom:2px;}
902
960
 
903
961
  @media (max-width:1100px){
904
962
  .dshgt-app{grid-template-columns:190px minmax(0,1fr);}
@@ -1117,6 +1175,17 @@ function getPreview(runId, signal) {
1117
1175
  function getLog(runId, signal) {
1118
1176
  return apiCall("GET", `/log?id=${encodeURIComponent(runId)}`, void 0, signal === void 0 ? {} : { signal });
1119
1177
  }
1178
+ async function getPluginUpdate(options = {}) {
1179
+ const query = options.fresh === true ? "?fresh=1" : "";
1180
+ const payload = await apiCall("GET", `/update${query}`, void 0, {
1181
+ ...options.signal === void 0 ? {} : { signal: options.signal }
1182
+ });
1183
+ return payload.update;
1184
+ }
1185
+ async function applyPluginUpdate() {
1186
+ const payload = await apiCall("POST", "/update", {});
1187
+ return payload.result;
1188
+ }
1120
1189
 
1121
1190
  // client/store.ts
1122
1191
  var POLL_MS = 5e3;
@@ -1931,6 +2000,196 @@ function LiveRun(props) {
1931
2000
  );
1932
2001
  }
1933
2002
 
2003
+ // node_modules/@morehao/dsh-plugin-kit/dist/update-client.js
2004
+ var IDLE = { status: "idle", view: null, error: null };
2005
+ function errorOf(error) {
2006
+ if (error !== null && typeof error === "object") {
2007
+ const value = error;
2008
+ const message = typeof value.message === "string" && value.message !== "" ? value.message : "操作失败";
2009
+ return typeof value.code === "string" ? { code: value.code, message } : { message };
2010
+ }
2011
+ return { message: String(error ?? "操作失败") };
2012
+ }
2013
+ function createUpdateController(options) {
2014
+ const now = options.now ?? Date.now;
2015
+ let state = { ...IDLE };
2016
+ const listeners = /* @__PURE__ */ new Set();
2017
+ let inFlight = null;
2018
+ function emit(next) {
2019
+ state = next;
2020
+ options.onChange?.(state);
2021
+ for (const listener of listeners) listener(state);
2022
+ }
2023
+ async function check(checkOptions = {}) {
2024
+ if (inFlight !== null) return;
2025
+ inFlight = "check";
2026
+ emit({ ...state, status: "checking", error: null });
2027
+ try {
2028
+ const view = await options.request.check(checkOptions);
2029
+ const checkedAt = new Date(now()).toISOString();
2030
+ if (view.checkFailed) {
2031
+ emit({
2032
+ status: "failed",
2033
+ view,
2034
+ error: { code: "CHECK_FAILED", message: "检查更新失败:拿不到 registry 上的最新版本" },
2035
+ checkedAt
2036
+ });
2037
+ } else {
2038
+ emit({ status: view.updateAvailable ? "available" : "current", view, error: null, checkedAt });
2039
+ }
2040
+ } catch (error) {
2041
+ emit({ ...state, status: "failed", error: errorOf(error) });
2042
+ } finally {
2043
+ inFlight = null;
2044
+ }
2045
+ }
2046
+ async function apply2() {
2047
+ if (inFlight !== null) return;
2048
+ const view = state.view;
2049
+ if (view === null || !view.updateAvailable) {
2050
+ emit({ ...state, status: "failed", error: { code: "NO_UPDATE", message: "没有可更新的版本,请先检查更新" } });
2051
+ return;
2052
+ }
2053
+ if (!view.canApply) {
2054
+ emit({
2055
+ ...state,
2056
+ status: "failed",
2057
+ error: { code: "NO_APPLY", message: `当前环境不支持一键更新,请执行:${view.manualCommand}` }
2058
+ });
2059
+ return;
2060
+ }
2061
+ inFlight = "apply";
2062
+ emit({ ...state, status: "applying", error: null });
2063
+ try {
2064
+ const result = await options.request.apply();
2065
+ const restartRequired = result.restartRequired ?? result.application !== "applied";
2066
+ emit({ ...state, status: restartRequired ? "restart-required" : "applied", error: null });
2067
+ } catch (error) {
2068
+ emit({ ...state, status: "failed", error: errorOf(error) });
2069
+ } finally {
2070
+ inFlight = null;
2071
+ }
2072
+ }
2073
+ return {
2074
+ getState: () => state,
2075
+ subscribe: (listener) => {
2076
+ listeners.add(listener);
2077
+ return () => {
2078
+ listeners.delete(listener);
2079
+ };
2080
+ },
2081
+ check,
2082
+ apply: apply2,
2083
+ reset: () => emit({ ...IDLE })
2084
+ };
2085
+ }
2086
+
2087
+ // client/update-panel.ts
2088
+ var singleton = null;
2089
+ function getUpdateController() {
2090
+ if (singleton === null) {
2091
+ singleton = createUpdateController({
2092
+ request: {
2093
+ check: (options) => getPluginUpdate(options.fresh === true ? { fresh: true } : {}),
2094
+ apply: () => applyPluginUpdate()
2095
+ }
2096
+ });
2097
+ }
2098
+ return singleton;
2099
+ }
2100
+ function usePluginUpdate() {
2101
+ const controller = getUpdateController();
2102
+ const [state, setState] = import_react.default.useState(() => controller.getState());
2103
+ import_react.default.useEffect(() => {
2104
+ const unsubscribe = controller.subscribe(setState);
2105
+ if (controller.getState().status === "idle") void controller.check();
2106
+ return unsubscribe;
2107
+ }, [controller]);
2108
+ return [state, controller];
2109
+ }
2110
+ function UpdateDot(props) {
2111
+ const [state] = usePluginUpdate();
2112
+ if (state.view?.updateAvailable !== true) return null;
2113
+ return h("span", { className: "dshgt-update-dot", title: props.t("update.badge") });
2114
+ }
2115
+ function PluginUpdateSection(props) {
2116
+ const { t } = props;
2117
+ const [state, controller] = usePluginUpdate();
2118
+ const [confirming, setConfirming] = import_react.default.useState(false);
2119
+ const [copied, setCopied] = import_react.default.useState(false);
2120
+ import_react.default.useEffect(() => {
2121
+ if (state.status === "applying" || state.status === "restart-required" || state.status === "applied") setConfirming(false);
2122
+ }, [state.status]);
2123
+ const copyCommand = () => {
2124
+ const command = state.view?.manualCommand;
2125
+ if (command === void 0) return;
2126
+ void copyText(command).then((ok) => {
2127
+ if (!ok) return;
2128
+ setCopied(true);
2129
+ setTimeout(() => setCopied(false), 1600);
2130
+ });
2131
+ };
2132
+ const view = state.view;
2133
+ const status = state.status;
2134
+ const target = view !== null && view.updateAvailable ? view.latestVersion ?? "" : "";
2135
+ const busy = status === "checking" || status === "applying";
2136
+ const settled = status === "restart-required" || status === "applied";
2137
+ const actionable = view !== null && view.updateAvailable && view.canApply && !settled && !busy;
2138
+ const manual = view !== null && (!view.canApply || view.checkFailed) ? view.manualCommand : null;
2139
+ const statusLine = () => {
2140
+ if (status === "checking" || view === null) return t("about.update.checking");
2141
+ if (status === "applying") return t("about.update.applying", { version: target });
2142
+ if (status === "restart-required") return t("about.update.done", { version: view.latestVersion ?? target });
2143
+ if (status === "applied") return t("about.update.applied", { version: view.latestVersion ?? target });
2144
+ if (status === "failed") {
2145
+ if (state.error?.code === "CHECK_FAILED") return t("about.update.checkFailed");
2146
+ return t("about.update.failed", { message: state.error?.message ?? "" });
2147
+ }
2148
+ if (view.checkFailed) return t("about.update.checkFailed");
2149
+ if (view.updateAvailable) return t("about.update.available", { latest: view.latestVersion ?? "", current: view.currentVersion });
2150
+ return t("about.update.current", { version: view.currentVersion });
2151
+ };
2152
+ const actions = [
2153
+ h(Btn, { key: "check", small: true, disabled: busy, onClick: () => void controller.check({ fresh: true }) }, t("about.update.check")),
2154
+ manual === null ? null : h(Btn, { key: "copy", small: true, onClick: copyCommand }, copied ? t("about.update.copied") : t("about.update.copy")),
2155
+ actionable ? confirming ? [
2156
+ h(
2157
+ Btn,
2158
+ {
2159
+ key: "confirm",
2160
+ small: true,
2161
+ variant: "danger",
2162
+ onClick: () => {
2163
+ setConfirming(false);
2164
+ void controller.apply();
2165
+ }
2166
+ },
2167
+ t("about.update.confirmYes")
2168
+ ),
2169
+ h(Btn, { key: "cancel", small: true, onClick: () => setConfirming(false) }, t("about.update.cancel"))
2170
+ ] : h(Btn, { key: "apply", small: true, variant: "primary", onClick: () => setConfirming(true) }, t("about.update.apply", { version: target })) : null
2171
+ ].filter((item) => item !== null);
2172
+ return h(
2173
+ "div",
2174
+ { className: `dshgt-update${props.bare === true ? " dshgt-update-bare" : ""}` },
2175
+ props.bare === true ? null : h(
2176
+ "div",
2177
+ { className: "dshgt-update-head" },
2178
+ h("h4", null, t("about.update.title")),
2179
+ h("span", { className: "dshgt-hint" }, t("about.update.hint"))
2180
+ ),
2181
+ h("p", { className: `dshgt-update-line${status === "failed" ? " dshgt-update-err" : ""}` }, statusLine()),
2182
+ confirming ? h("p", { className: "dshgt-update-warn" }, t("about.update.confirm", { version: target })) : null,
2183
+ h("div", { className: "dshgt-update-actions" }, actions),
2184
+ manual === null ? null : h(
2185
+ "div",
2186
+ { className: "dshgt-update-manual" },
2187
+ h("p", { className: "dshgt-update-warn" }, t("about.update.manual", { kind: view?.installKind ?? "unknown" })),
2188
+ h("code", { className: "dshgt-update-cmd" }, manual)
2189
+ )
2190
+ );
2191
+ }
2192
+
1934
2193
  // client/digest-core.ts
1935
2194
  var DIGEST_WINDOW_DAYS = 30;
1936
2195
  var DATE_RE = /^(\d{4})-(\d{2})-(\d{2})$/;
@@ -2264,6 +2523,12 @@ function OverviewView(props) {
2264
2523
  )
2265
2524
  );
2266
2525
  })
2526
+ ),
2527
+ h(
2528
+ "section",
2529
+ { className: "dshgt-card dshgt-update-card" },
2530
+ h("div", { className: "dshgt-card-h" }, h("h3", null, t("about.update.title"))),
2531
+ h(PluginUpdateSection, { t, bare: true })
2267
2532
  )
2268
2533
  )
2269
2534
  );
@@ -2570,9 +2835,72 @@ var HR = /^\s*(?:-{3,}|\*{3,}|_{3,})\s*$/;
2570
2835
  var QUOTE = /^\s*>\s?(.*)$/;
2571
2836
  var BULLET = /^\s*[-*+]\s+(.*)$/;
2572
2837
  var ORDERED = /^\s*\d+[.)]\s+(.*)$/;
2838
+ var TABLE_DELIM_CELL = /^:?-+:?$/;
2573
2839
  function isBlockStart(line) {
2574
2840
  return FENCE.test(line) || HEADING.test(line) || HR.test(line) || QUOTE.test(line) || BULLET.test(line) || ORDERED.test(line);
2575
2841
  }
2842
+ function splitTableRow(line) {
2843
+ const cells = [];
2844
+ let current = "";
2845
+ let inCode = false;
2846
+ const text = line.trim();
2847
+ for (let cursor = 0; cursor < text.length; cursor += 1) {
2848
+ const char = text[cursor] ?? "";
2849
+ if (char === "\\" && text[cursor + 1] === "|") {
2850
+ current += "|";
2851
+ cursor += 1;
2852
+ continue;
2853
+ }
2854
+ if (char === "`") {
2855
+ inCode = !inCode;
2856
+ current += char;
2857
+ continue;
2858
+ }
2859
+ if (char === "|" && !inCode) {
2860
+ cells.push(current);
2861
+ current = "";
2862
+ continue;
2863
+ }
2864
+ current += char;
2865
+ }
2866
+ cells.push(current);
2867
+ if (cells.length > 1 && (cells[0] ?? "").trim() === "") cells.shift();
2868
+ if (cells.length > 1 && (cells[cells.length - 1] ?? "").trim() === "") cells.pop();
2869
+ return cells.map((cell) => cell.trim());
2870
+ }
2871
+ function parseAlignRow(line) {
2872
+ const cells = splitTableRow(line);
2873
+ if (cells.length === 0) return null;
2874
+ const align = [];
2875
+ for (const cell of cells) {
2876
+ const value = cell.trim();
2877
+ if (!TABLE_DELIM_CELL.test(value)) return null;
2878
+ const left = value.startsWith(":");
2879
+ const right = value.endsWith(":");
2880
+ align.push(left && right ? "center" : right ? "right" : left ? "left" : void 0);
2881
+ }
2882
+ return align;
2883
+ }
2884
+ function tableAt(lines, index) {
2885
+ const headerLine = lines[index] ?? "";
2886
+ if (!headerLine.includes("|")) return null;
2887
+ const align = parseAlignRow(lines[index + 1] ?? "");
2888
+ if (align === null) return null;
2889
+ const headerCells = splitTableRow(headerLine);
2890
+ if (headerCells.length === 0 || headerCells.length !== align.length) return null;
2891
+ const rows = [];
2892
+ let cursor = index + 2;
2893
+ while (cursor < lines.length) {
2894
+ const line = lines[cursor] ?? "";
2895
+ if (line.trim() === "" || !line.includes("|")) break;
2896
+ if (isBlockStart(line)) break;
2897
+ const cells = splitTableRow(line).slice(0, headerCells.length);
2898
+ while (cells.length < headerCells.length) cells.push("");
2899
+ rows.push(cells.map((cell) => parseInline(cell)));
2900
+ cursor += 1;
2901
+ }
2902
+ return { align, header: headerCells.map((cell) => parseInline(cell)), rows, next: cursor };
2903
+ }
2576
2904
  function parseBlocks(body) {
2577
2905
  const lines = body.split("\n");
2578
2906
  const blocks = [];
@@ -2644,11 +2972,17 @@ function parseBlocks(body) {
2644
2972
  blocks.push({ type: "list", ordered: true, items });
2645
2973
  continue;
2646
2974
  }
2975
+ const table = tableAt(lines, index);
2976
+ if (table !== null) {
2977
+ blocks.push({ type: "table", align: table.align, header: table.header, rows: table.rows });
2978
+ index = table.next;
2979
+ continue;
2980
+ }
2647
2981
  const buffer = [];
2648
2982
  while (index < lines.length) {
2649
2983
  const current = lines[index] ?? "";
2650
2984
  if (current.trim() === "") break;
2651
- if (buffer.length > 0 && isBlockStart(current)) break;
2985
+ if (buffer.length > 0 && (isBlockStart(current) || tableAt(lines, index) !== null)) break;
2652
2986
  buffer.push(current.trim());
2653
2987
  index += 1;
2654
2988
  }
@@ -2681,6 +3015,9 @@ function renderInline(nodes, prefix) {
2681
3015
  }
2682
3016
  });
2683
3017
  }
3018
+ function alignStyle(align) {
3019
+ return align === void 0 ? void 0 : { textAlign: align };
3020
+ }
2684
3021
  function renderBlock(block, key) {
2685
3022
  switch (block.type) {
2686
3023
  case "heading": {
@@ -2705,6 +3042,43 @@ function renderBlock(block, key) {
2705
3042
  );
2706
3043
  case "hr":
2707
3044
  return h("hr", { key, className: "dshgt-md-hr" });
3045
+ case "table":
3046
+ return h(
3047
+ "div",
3048
+ { key, className: "dshgt-md-tablewrap" },
3049
+ h(
3050
+ "table",
3051
+ { className: "dshgt-md-table" },
3052
+ h(
3053
+ "thead",
3054
+ null,
3055
+ h(
3056
+ "tr",
3057
+ null,
3058
+ block.header.map(
3059
+ (cell, index) => h("th", { key: `h${index}`, style: alignStyle(block.align[index]) }, renderInline(cell, `${key}-h${index}`))
3060
+ )
3061
+ )
3062
+ ),
3063
+ h(
3064
+ "tbody",
3065
+ null,
3066
+ block.rows.map(
3067
+ (row, rowIndex) => h(
3068
+ "tr",
3069
+ { key: `r${rowIndex}` },
3070
+ row.map(
3071
+ (cell, cellIndex) => h(
3072
+ "td",
3073
+ { key: `c${cellIndex}`, style: alignStyle(block.align[cellIndex]) },
3074
+ renderInline(cell, `${key}-r${rowIndex}c${cellIndex}`)
3075
+ )
3076
+ )
3077
+ )
3078
+ )
3079
+ )
3080
+ )
3081
+ );
2708
3082
  }
2709
3083
  }
2710
3084
  function renderMarkdown(markdown) {
@@ -3029,7 +3403,7 @@ function AboutDialog(props) {
3029
3403
  setTimeout(() => setCopied(false), 1600);
3030
3404
  });
3031
3405
  };
3032
- return h(
3406
+ const dialog = h(
3033
3407
  "div",
3034
3408
  { className: "dshgt-modal-mask", onClick: (event) => {
3035
3409
  if (event.target === event.currentTarget) props.onClose();
@@ -3059,9 +3433,11 @@ function AboutDialog(props) {
3059
3433
  [t("about.core"), state.core.version === void 0 ? "—" : `trendradar ${state.core.version}`],
3060
3434
  [t("about.env"), t("about.env.value")]
3061
3435
  ]
3062
- })
3436
+ }),
3437
+ h(PluginUpdateSection, { t })
3063
3438
  )
3064
3439
  );
3440
+ return h("div", { className: "dshgt-root" }, dialog);
3065
3441
  }
3066
3442
 
3067
3443
  // client/panel-icon.ts
@@ -3306,7 +3682,8 @@ function apply(ctx) {
3306
3682
  onClick: () => setAboutOpen(true),
3307
3683
  title: plugin === void 0 ? "" : fullName
3308
3684
  },
3309
- plugin?.name ?? t("section.title")
3685
+ plugin?.name ?? t("section.title"),
3686
+ h(UpdateDot, { t })
3310
3687
  ),
3311
3688
  h(
3312
3689
  "div",
@@ -3096,6 +3096,7 @@ __export(routes_exports, {
3096
3096
  ROUTE_PREFIX: () => ROUTE_PREFIX,
3097
3097
  createRoutes: () => createRoutes
3098
3098
  });
3099
+ import { PluginUpdateError } from "@morehao/dsh-plugin-kit/update";
3099
3100
  var ROUTE_PREFIX = "/api/morehao/github-trending";
3100
3101
  var HEADER = "x-dsh-github-trending";
3101
3102
  var MAX_BODY2 = 512 * 1024;
@@ -3143,6 +3144,17 @@ function createRoutes(deps) {
3143
3144
  if (!response.writableEnded) sendJson2(response, 200, { ok: true, ...result });
3144
3145
  } catch (error) {
3145
3146
  if (response.writableEnded) return;
3147
+ if (error instanceof PluginUpdateError) {
3148
+ deps.logger?.("update.failed", { code: error.code, message: error.message });
3149
+ sendJson2(response, 409, {
3150
+ ok: false,
3151
+ code: "UPDATE_FAILED",
3152
+ updateCode: error.code,
3153
+ message: redact(error.message),
3154
+ update: deps.update?.view() ?? null
3155
+ });
3156
+ return;
3157
+ }
3146
3158
  const code = error instanceof IpcError ? error.code : "INTERNAL";
3147
3159
  const message = redact(error instanceof Error ? error.message : String(error));
3148
3160
  deps.logger?.("route.error", { pathname, method, code, message });
@@ -3155,6 +3167,18 @@ async function dispatch(method, pathname, url, request, deps) {
3155
3167
  const env = deps.env ?? process.env;
3156
3168
  const settings = deps.store.readSettings();
3157
3169
  if (method === "GET" && pathname === "/state") return statePayload(deps, settings);
3170
+ if (method === "GET" && pathname === "/update") {
3171
+ if (deps.update === void 0) throw new IpcError("NOT_FOUND", "\u81EA\u66F4\u65B0\u670D\u52A1\u4E0D\u53EF\u7528");
3172
+ const update = await deps.update.check({ fresh: url.searchParams.get("fresh") === "1" });
3173
+ return { update };
3174
+ }
3175
+ if (method === "POST" && pathname === "/update") {
3176
+ if (deps.update === void 0) throw new IpcError("NOT_FOUND", "\u81EA\u66F4\u65B0\u670D\u52A1\u4E0D\u53EF\u7528");
3177
+ const body = await readBody2(request);
3178
+ const version = typeof body.version === "string" && body.version.trim() !== "" ? body.version.trim() : void 0;
3179
+ const result = await deps.update.apply(version === void 0 ? {} : { version });
3180
+ return { result, update: deps.update.view() };
3181
+ }
3158
3182
  if (method === "GET" && pathname === "/core") {
3159
3183
  const core = await inspectCore(settings, { env, fresh: url.searchParams.get("fresh") === "1" });
3160
3184
  const coexistence = await detectCoexistence(core.path, { env });
@@ -3347,6 +3371,57 @@ async function statePayload(deps, settings) {
3347
3371
  };
3348
3372
  }
3349
3373
 
3374
+ // src/host/update.ts
3375
+ var update_exports = {};
3376
+ __export(update_exports, {
3377
+ createUpdateService: () => createUpdateService
3378
+ });
3379
+ import { normalizeProfile } from "@morehao/dsh-plugin-kit";
3380
+ import { createPluginUpdater } from "@morehao/dsh-plugin-kit/update";
3381
+ function pnpmViewRunner(get, env, exec) {
3382
+ return async (spec) => {
3383
+ const profile = normalizeProfile(get("profileContext"));
3384
+ if (profile === null || profile.dir === void 0) return null;
3385
+ const command = profile.packageManager?.command ?? "pnpm";
3386
+ const prefix = profile.packageManager?.args ?? [];
3387
+ let result;
3388
+ try {
3389
+ result = await exec(command, [...prefix, "view", spec, "version", "--json"], {
3390
+ cwd: profile.dir,
3391
+ env: sanitizedEnv(env),
3392
+ timeoutMs: 8e3,
3393
+ maxStdout: 64 * 1024,
3394
+ maxStderr: 16 * 1024
3395
+ });
3396
+ } catch {
3397
+ return null;
3398
+ }
3399
+ if (result.code !== 0) return null;
3400
+ const text = result.stdout.trim();
3401
+ if (text === "") return null;
3402
+ let parsed;
3403
+ try {
3404
+ parsed = JSON.parse(text);
3405
+ } catch {
3406
+ return null;
3407
+ }
3408
+ const value = Array.isArray(parsed) ? parsed[parsed.length - 1] : parsed;
3409
+ return typeof value === "string" && value.trim() !== "" ? value.trim() : null;
3410
+ };
3411
+ }
3412
+ function createUpdateService(deps) {
3413
+ const exec = deps.exec ?? execCapture;
3414
+ return createPluginUpdater({
3415
+ meta: deps.meta,
3416
+ getPluginManager: () => deps.get("pluginManager"),
3417
+ getProfile: () => deps.get("profileContext"),
3418
+ runPnpmView: pnpmViewRunner(deps.get, deps.env, exec),
3419
+ ...deps.fetchImpl === void 0 ? {} : { fetchImpl: deps.fetchImpl },
3420
+ ...deps.readFile === void 0 ? {} : { readFile: deps.readFile },
3421
+ ...deps.logger === void 0 ? {} : { logger: deps.logger }
3422
+ });
3423
+ }
3424
+
3350
3425
  // src/tools.ts
3351
3426
  var tools_exports = {};
3352
3427
  __export(tools_exports, {
@@ -3540,6 +3615,8 @@ export {
3540
3615
  ROUTE_PREFIX,
3541
3616
  createRoutes,
3542
3617
  routes_exports,
3618
+ createUpdateService,
3619
+ update_exports,
3543
3620
  createTrendingRunTool,
3544
3621
  createTrendingHistoryTool,
3545
3622
  tools_exports
package/lib/index.js CHANGED
@@ -6,8 +6,9 @@ import {
6
6
  createRoutes,
7
7
  createTrendingHistoryTool,
8
8
  createTrendingRunTool,
9
+ createUpdateService,
9
10
  readPluginInfo
10
- } from "./chunk-X4PBXIZE.js";
11
+ } from "./chunk-NMINPPWO.js";
11
12
  import "./chunk-XLIFTAVL.js";
12
13
 
13
14
  // src/index.ts
@@ -59,7 +60,18 @@ function apply(ctx, config = {}) {
59
60
  onError: (error) => ctx.logger?.warn?.(`[dsh-github-trending] \u5B9A\u65F6\u8FD0\u884C\u5931\u8D25\uFF1A${error instanceof Error ? error.message : String(error)}`)
60
61
  });
61
62
  const pluginInfo = readPluginInfo();
63
+ const update = createUpdateService({
64
+ get,
65
+ env,
66
+ meta: {
67
+ name: pluginInfo.name,
68
+ version: VERSION,
69
+ ...pluginInfo.repository === void 0 ? {} : { repository: pluginInfo.repository }
70
+ },
71
+ logger: log
72
+ });
62
73
  const handler = createRoutes({
74
+ update,
63
75
  store,
64
76
  runner,
65
77
  scheduler,
package/lib/internal.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { ErrorCode, FileArtifact, ErrorInfo, RunRequest, RunResult, State, StateRun } from '@morehao/trendradar-contract';
2
2
  import http from 'node:http';
3
+ import { Updater, PluginMeta, Logger } from '@morehao/dsh-plugin-kit/update';
3
4
 
4
5
  /**
5
6
  * 错误码:核心码(契约包 `ipc.v2` / `errors.v2` 的 17 个)+ 插件自己的码。
@@ -1377,6 +1378,8 @@ interface RouteDeps {
1377
1378
  /** 注入用:pendingPr 对账的 fetch 实现(测试不联网)。 */
1378
1379
  fetchImpl?: typeof fetch;
1379
1380
  version: string;
1381
+ /** 自更新服务(共享包引擎);缺省时 /update 返回 404,不影响其它路由。 */
1382
+ update?: Updater;
1380
1383
  /** 包名与仓库地址都来自 package.json,由 src/index.ts 传入。 */
1381
1384
  pluginName?: string;
1382
1385
  repository?: string;
@@ -1392,6 +1395,24 @@ declare namespace routes {
1392
1395
  export { routes_HEADER as HEADER, routes_ROUTE_PREFIX as ROUTE_PREFIX, type routes_RouteDeps as RouteDeps, routes_createRoutes as createRoutes };
1393
1396
  }
1394
1397
 
1398
+ interface UpdateServiceDeps {
1399
+ get: (name: string) => unknown;
1400
+ env: NodeJS.ProcessEnv;
1401
+ meta: PluginMeta;
1402
+ logger?: Logger;
1403
+ fetchImpl?: typeof fetch;
1404
+ /** 测试注入;默认走本仓的 execCapture。 */
1405
+ exec?: (file: string, args: string[], options?: ExecOptions) => Promise<ExecResult>;
1406
+ readFile?: (path: string) => Promise<string>;
1407
+ }
1408
+ declare function createUpdateService(deps: UpdateServiceDeps): Updater;
1409
+
1410
+ type update_UpdateServiceDeps = UpdateServiceDeps;
1411
+ declare const update_createUpdateService: typeof createUpdateService;
1412
+ declare namespace update {
1413
+ export { type update_UpdateServiceDeps as UpdateServiceDeps, update_createUpdateService as createUpdateService };
1414
+ }
1415
+
1395
1416
  interface ImportCandidate {
1396
1417
  path: string;
1397
1418
  exists: boolean;
@@ -1633,4 +1654,4 @@ declare namespace tools {
1633
1654
  export { tools_createTrendingHistoryTool as createTrendingHistoryTool, tools_createTrendingRunTool as createTrendingRunTool };
1634
1655
  }
1635
1656
 
1636
- export { channel, config, core, credentials, errors, exec, github, handshake$1 as handshake, importCli, ipc, locate, paths, pendingPr, pluginInfo, preflight, proxy, quickfill, release, repo, routes, run, runner, schedule, state, tools };
1657
+ export { channel, config, core, credentials, errors, exec, github, handshake$1 as handshake, importCli, ipc, locate, paths, pendingPr, pluginInfo, preflight, proxy, quickfill, release, repo, routes, run, runner, schedule, state, tools, update as updateService };
package/lib/internal.js CHANGED
@@ -22,8 +22,9 @@ import {
22
22
  runner_exports,
23
23
  schedule_exports,
24
24
  state_exports,
25
- tools_exports
26
- } from "./chunk-X4PBXIZE.js";
25
+ tools_exports,
26
+ update_exports
27
+ } from "./chunk-NMINPPWO.js";
27
28
  import {
28
29
  exec_exports
29
30
  } from "./chunk-XLIFTAVL.js";
@@ -52,5 +53,6 @@ export {
52
53
  runner_exports as runner,
53
54
  schedule_exports as schedule,
54
55
  state_exports as state,
55
- tools_exports as tools
56
+ tools_exports as tools,
57
+ update_exports as updateService
56
58
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@morehao/dsh-github-trending",
3
- "version": "0.3.0",
3
+ "version": "0.3.2",
4
4
  "description": "GitHub 趋势日报插件:每天定时 spawn 本地 trendradar 核心,抓取 Trending、生成中文简介与总结,提交到你的仓库并自动合并 PR。",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
@@ -50,6 +50,7 @@
50
50
  }
51
51
  },
52
52
  "dependencies": {
53
+ "@morehao/dsh-plugin-kit": "0.1.0",
53
54
  "@morehao/trendradar-contract": "0.2.0"
54
55
  },
55
56
  "dsh": {