dsh-plugin 1.3.0 → 1.3.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-plugin",
3
- "version": "1.3.0",
3
+ "version": "1.3.1",
4
4
  "description": "A community plugin marketplace for DeepSeek Harness, built to the official plugin spec — browse, search and install 4000+ human-curated community plugins without leaving the app. · DeepSeek Harness 社区插件市场(遵循官方开发规范):4000+ 人工精选社区插件,每日更新。",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
@@ -36,7 +36,7 @@ import { CustomInstallView } from './views/CustomInstallView.tsx'
36
36
  import { SettingsView } from './views/SettingsView.tsx'
37
37
  import { CloseIcon } from './ui/icons.tsx'
38
38
  import type { InstalledItem } from '../logic/installed.ts'
39
- import { pluginOfItem } from '../logic/installed.ts'
39
+ import { issueRepoOf, pluginOfItem } from '../logic/installed.ts'
40
40
 
41
41
  export function PluginHubSection({ t: _hostT, locale }: SectionProps) {
42
42
  const { lang, langKey, langPath, t, toggleLang } = useLanguage(locale)
@@ -116,9 +116,12 @@ export function PluginHubSection({ t: _hostT, locale }: SectionProps) {
116
116
  setNotifications(addSuccess({ kind: 'uninstall', repo: repo ?? '' }))
117
117
  },
118
118
  onError: (message, repo, kind, command, attempts) => {
119
- setErrorMsg({ message, repo, kind, command, attempts })
119
+ // 提 Issue 目标先反查仓库身份:npm 包名能在已安装表/目录里反查到 owner/repo 才保留,
120
+ // 反查不到置 null —— 错误弹窗/通知中心据此不显示「一键提 Issue」按钮(防提到错误仓库)
121
+ const issueRepo = issueRepoOf(repo ?? '', catalog.installedItems, catalog.plugins)
122
+ setErrorMsg({ message, repo: issueRepo, kind, command, attempts })
120
123
  // 失败自动写入通知记录:任务在后台结束时没人盯着也能留痕
121
- setNotifications(addFailure({ kind, repo: repo ?? '', message, command, attempts }))
124
+ setNotifications(addFailure({ kind, repo: issueRepo ?? '', message, command, attempts }))
122
125
  },
123
126
  installPlugin: confirmPlugin,
124
127
  installCustomTarget: confirmCustomTarget,
@@ -237,7 +240,12 @@ export function PluginHubSection({ t: _hostT, locale }: SectionProps) {
237
240
  (i.repo !== null && i.repo.toLowerCase() === needle) ||
238
241
  repoFromInstallTarget(i.spec).toLowerCase() === needle)
239
242
  }
240
- return catalog.installedItems.some((i) => i.name === target)
243
+ // npm 包名(可带 @version):剥掉版本号后同时查已安装列表与原始依赖表。
244
+ // Hub 自身(file:/link: 安装)被 installedItems 刻意排除(防误卸载),列表里查不到,
245
+ // 但依赖表真实存在——补齐后「命令窗口重装 hub 自身」也能转「更新」语义,不再撞 already installed
246
+ const pkg = repo.replace(/@[^@]*$/, '').toLowerCase()
247
+ return catalog.installedItems.some((i) => i.name.toLowerCase() === pkg) ||
248
+ Object.keys(catalog.installed).some((k) => k.toLowerCase() === pkg)
241
249
  }
242
250
 
243
251
  /** 弹窗动作一:复制安装命令到剪贴板,引导去终端粘贴执行(npm 通道显示包名命令)。 */
@@ -502,6 +510,9 @@ export function PluginHubSection({ t: _hostT, locale }: SectionProps) {
502
510
  cancelTask: queue.cancelTask,
503
511
  restarting,
504
512
  onRestart: () => { void requestRestart() },
513
+ // 失败记录仓库反查:历史记录可能存的是裸 npm 包名,反查成 owner/repo 才显示仓库链接
514
+ // 与提 Issue 按钮;查不到则隐藏(防提到错误仓库)
515
+ resolveRepo: (repo: string) => issueRepoOf(repo, catalog.installedItems, catalog.plugins),
505
516
  }),
506
517
  // 「忽略本次更新」确认弹窗:确认后该版本写入忽略集(持久化)并从通知中心移除,
507
518
  // 本次版本不再提醒,直到插件发布下一个新版本
@@ -55,7 +55,7 @@ function BadgeGlyph({ ok }: { ok: boolean }) {
55
55
  }))
56
56
  }
57
57
 
58
- export function NotificationsModal({ records, tasks, pendingRestarts, t, env, onClose, onCopy, onClear, onRemove, onUpdate, onIgnoreUpdate, cancelTask, restarting, onRestart }: {
58
+ export function NotificationsModal({ records, tasks, pendingRestarts, t, env, onClose, onCopy, onClear, onRemove, onUpdate, onIgnoreUpdate, cancelTask, restarting, onRestart, resolveRepo }: {
59
59
  records: NotificationRecord[]
60
60
  /** 进行中的安装/卸载任务(实时进度,与队列弹窗同一数据源) */
61
61
  tasks: QueueTask[]
@@ -75,6 +75,9 @@ export function NotificationsModal({ records, tasks, pendingRestarts, t, env, on
75
75
  cancelTask: (id: number) => void
76
76
  restarting: boolean
77
77
  onRestart: () => void
78
+ /** 失败记录 repo 反查:npm 包名(历史记录存的是裸包名)反查成 owner/repo 再显示/提 Issue;
79
+ * 查不到返回 null,按钮与仓库链接一并隐藏(防提到错误仓库)。不传则按记录原样展示。 */
80
+ resolveRepo?: (repo: string) => string | null
78
81
  }) {
79
82
  return h('div', {
80
83
  className: styles.overlay,
@@ -165,6 +168,9 @@ export function NotificationsModal({ records, tasks, pendingRestarts, t, env, on
165
168
  : h('div', { className: styles.noticeList },
166
169
  records.map((r) => {
167
170
  const isUpdate = r.kind === 'update'
171
+ // 展示用仓库:历史记录存的可能是裸 npm 包名,反查成 owner/repo 才显示/提 Issue;
172
+ // 查不到为空 → 仓库链接与提 Issue 按钮都不渲染
173
+ const shownRepo = resolveRepo ? (resolveRepo(r.repo) ?? '') : r.repo
168
174
  return h('div', {
169
175
  key: r.id,
170
176
  // 更新提醒:整行可点击直达更新确认弹窗;install/uninstall 记录维持原交互
@@ -191,14 +197,16 @@ export function NotificationsModal({ records, tasks, pendingRestarts, t, env, on
191
197
  r.repo
192
198
  ? (isUpdate
193
199
  ? h('span', { className: styles.failRepo, title: r.repo }, r.repo)
194
- : h('a', {
195
- className: styles.failRepo,
196
- // 跳转到官网详情页(含插件收录信息),不在通知弹窗直接跳 GitHub
197
- href: pluginSiteUrl(r.repo),
198
- target: '_blank',
199
- rel: 'noopener noreferrer',
200
- title: r.repo,
201
- }, r.repo))
200
+ : shownRepo
201
+ ? h('a', {
202
+ className: styles.failRepo,
203
+ // 跳转到官网详情页(含插件收录信息),不在通知弹窗直接跳 GitHub
204
+ href: pluginSiteUrl(shownRepo),
205
+ target: '_blank',
206
+ rel: 'noopener noreferrer',
207
+ title: shownRepo,
208
+ }, shownRepo)
209
+ : null)
202
210
  : null,
203
211
  !r.ok && h('button', {
204
212
  className: styles.failCopy,
@@ -252,18 +260,18 @@ export function NotificationsModal({ records, tasks, pendingRestarts, t, env, on
252
260
  h('div', { className: styles.failPrepareHint }, kind === 'pnpmIgnoredBuild'
253
261
  ? t('failIgnoredBuild')
254
262
  : /\[packaging\]/i.test(r.message) ? t('failPackagingHint') : t('failPrepareHint')),
255
- r.repo ? h('a', {
263
+ r.repo && shownRepo ? h('a', {
256
264
  className: styles.failBigIssue,
257
- href: pluginIssueUrl(r.repo, r.message, env, r.command, r.attempts),
265
+ href: pluginIssueUrl(shownRepo, r.message, env, r.command, r.attempts),
258
266
  target: '_blank',
259
267
  rel: 'noopener noreferrer',
260
268
  title: t('failIssueHint'),
261
269
  }, t('failIssueBig')) : null,
262
270
  ])
263
271
  }
264
- return r.repo ? h('a', {
272
+ return r.repo && shownRepo ? h('a', {
265
273
  className: styles.failBigIssue,
266
- href: pluginIssueUrl(r.repo, r.message, env, r.command, r.attempts),
274
+ href: pluginIssueUrl(shownRepo, r.message, env, r.command, r.attempts),
267
275
  target: '_blank',
268
276
  rel: 'noopener noreferrer',
269
277
  title: t('failIssueHint'),
@@ -116,6 +116,40 @@ function isRepoLike(value: string): boolean {
116
116
  return /^[A-Za-z0-9._-]+\/[A-Za-z0-9._-]+$/.test(value)
117
117
  }
118
118
 
119
+ /**
120
+ * 提 Issue 的仓库身份解析:失败目标可能是 owner/repo 也可能是 npm 包名(含 @scope/pkg、
121
+ * 可带 @version,如 dsh-plugin、@scope/pkg@1.2.3)。
122
+ * - 目标本身就能解析出 owner/repo → 直接用;
123
+ * - npm 包名 → 反查仓库身份:先查已安装表(该包名的条目若带仓库身份即命中),再查目录插件
124
+ * (source.npmPackage 与包名一致,取目录收录的仓库身份)。
125
+ * 反查到才返回,查不到返回 null —— 调用方据此不显示「一键提 Issue」按钮,避免把 Issue
126
+ * 提到错误的地址(别人发的 npm 包名不是 GitHub 仓库,直接拼 github.com/<包名> 是打不开的)。
127
+ */
128
+ export function issueRepoOf(
129
+ target: string,
130
+ installedItems: InstalledItem[],
131
+ plugins: HubPlugin[] | null,
132
+ ): string | null {
133
+ const input = target.trim()
134
+ if (!input) return null
135
+ const parsed = repoFromInstallTarget(input)
136
+ if (isRepoLike(parsed)) return parsed
137
+ const pkg = parsed.replace(/@[^@]*$/, '').toLowerCase()
138
+ // 已安装表:包名命中的条目(目录收录条目直接带仓库身份;自定义安装按 github: spec 解析)
139
+ for (const item of installedItems) {
140
+ if (item.name.toLowerCase() !== pkg) continue
141
+ if (item.repo) return item.repo
142
+ const repo = repoFromInstallTarget(item.spec)
143
+ if (isRepoLike(repo)) return repo
144
+ }
145
+ // 目录插件:npmPackage 与包名一致 → 用目录收录的仓库身份
146
+ for (const p of plugins ?? []) {
147
+ const pkgName = (p.source?.npmPackage ?? '').trim()
148
+ if (pkgName !== '' && pkgName.toLowerCase() === pkg && p.source?.repo) return p.source.repo
149
+ }
150
+ return null
151
+ }
152
+
119
153
  /** 已安装项 → 弹窗可用的伪插件对象:卸载确认弹窗只读名称/来源行,
120
154
  * 自定义安装(目录外)没有完整目录数据,用它把 InstalledItem 适配成 HubPlugin。 */
121
155
  export function pluginOfItem(item: InstalledItem): HubPlugin {
@@ -139,7 +173,12 @@ function isHubSelf(name: string, spec: string): boolean {
139
173
  if (name !== 'dsh-plugin') return false
140
174
  if (spec.startsWith('file:') || spec.startsWith('link:')) return true
141
175
  const repo = repoFromInstallTarget(spec)
142
- return isRepoLike(repo) && repo.toLowerCase() === HUB_REPO.toLowerCase()
176
+ if (isRepoLike(repo) && repo.toLowerCase() === HUB_REPO.toLowerCase()) return true
177
+ // npm 通道覆盖重装 hub 自身后,file:/link: 本地链接会被替换成 registry 版本号/范围
178
+ // (如 1.3.0、^1.3.0)——依赖 key `dsh-plugin` 在 npm 上唯一属于本插件(不存在第三方
179
+ // 同名包),版本号形态与仓库身份同样视为 hub 自身:命令窗口「自己装自己」成功后,
180
+ // 条目仍不进已安装列表(卸载接口对它的 400 拒绝是服务端最后一道防线)。
181
+ return !isRepoLike(repo)
143
182
  }
144
183
 
145
184
  /**