dsh-plugin 2.2.4 → 2.2.5

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": "2.2.4",
3
+ "version": "2.2.5",
4
4
  "description": "A community plugin marketplace for DeepSeek Harness, built to the official plugin spec — browse, search and install 10000+ human-curated community plugins without leaving the app. · DeepSeek Harness 社区插件市场(遵循官方开发规范):10000+ 人工精选社区插件,每日更新。",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
@@ -13,7 +13,7 @@ import { siteUrl } from '../../logic/urls.ts'
13
13
  import { useIncrementalList } from '../../hooks/useIncrementalList.ts'
14
14
  import { PluginCard } from './PluginCard.tsx'
15
15
 
16
- export function CatalogList({ plugins, failed, visible, total, t, langPath, reload, forceReload, category, copied, installedName, installedVersion, hasUpdate, langKey, onInstall, onUninstall, hasProxy, onOpenDiagnostics }: {
16
+ export function CatalogList({ plugins, failed, visible, total, t, langPath, reload, forceReload, category, copied, installedName, installedVersion, hasUpdate, updatePendingUntil, langKey, onInstall, onUninstall, hasProxy, onOpenDiagnostics }: {
17
17
  plugins: HubPlugin[] | null
18
18
  failed: boolean
19
19
  visible: HubPlugin[]
@@ -28,6 +28,8 @@ export function CatalogList({ plugins, failed, visible, total, t, langPath, relo
28
28
  installedName: (p: HubPlugin) => string | null
29
29
  installedVersion: (p: HubPlugin) => string | null
30
30
  hasUpdate: (p: HubPlugin) => boolean
31
+ /** 更新被供应链门禁暂缓时的可重试时刻(毫秒);无则 null */
32
+ updatePendingUntil: (p: HubPlugin) => number | null
31
33
  langKey: LocaleId
32
34
  onInstall: (p: HubPlugin, opts?: { update?: boolean }) => void
33
35
  onUninstall: (p: HubPlugin) => void
@@ -72,6 +74,7 @@ export function CatalogList({ plugins, failed, visible, total, t, langPath, relo
72
74
  installedName,
73
75
  installedVersion,
74
76
  hasUpdate,
77
+ updatePendingUntil,
75
78
  t,
76
79
  langKey,
77
80
  langPath,
@@ -11,16 +11,18 @@ import styles from '../../styles/List.module.css'
11
11
  import type { HubPlugin, LocaleId, Translate } from '../../types.ts'
12
12
  import { CATEGORY_LABELS, categoryLabel } from '../../logic/constants.ts'
13
13
  import { pluginDetailUrl } from '../../logic/urls.ts'
14
- import { fmtStars, relTime } from '../../logic/format.ts'
14
+ import { fmtStars, clockTime, relTime } from '../../logic/format.ts'
15
15
 
16
- export function PluginCard({ plugin: p, copied, installedName, installedVersion, hasUpdate, t, langKey, langPath, onInstall, onUninstall }: {
16
+ export function PluginCard({ plugin: p, copied, installedName, installedVersion, hasUpdate, updatePendingUntil, t, langKey, langPath, onInstall, onUninstall }: {
17
17
  plugin: HubPlugin
18
18
  copied: string | null
19
19
  installedName: (p: HubPlugin) => string | null
20
20
  /** 已安装版本号(安装时从目录信号记录;无 release 的仓库为空,无版本号可展示) */
21
21
  installedVersion: (p: HubPlugin) => string | null
22
- /** 已安装且目录有更新(有版本比版本、无版本比仓库更新时间) */
22
+ /** 已安装且目录有更新(有版本比版本、无版本比仓库更新时间);门禁复核未就绪时为 false */
23
23
  hasUpdate: (p: HubPlugin) => boolean
24
+ /** 更新被供应链门禁暂缓时的可重试时刻(毫秒);无则 null */
25
+ updatePendingUntil: (p: HubPlugin) => number | null
24
26
  t: Translate
25
27
  langKey: LocaleId
26
28
  langPath: string
@@ -33,6 +35,8 @@ export function PluginCard({ plugin: p, copied, installedName, installedVersion,
33
35
  const pkg = installedName(p)
34
36
  const isInstalled = pkg !== null
35
37
  const update = hasUpdate(p)
38
+ // 门禁暂缓:更新确实存在但现在装不上,给确切时刻而不是让徽标静默消失
39
+ const pendingUntil = isInstalled && !update ? updatePendingUntil(p) : null
36
40
  // 版本号展示:已安装 → 当前已装版本(最能反映用户状态);未安装 → 目录最新版本
37
41
  const versionShown = isInstalled ? installedVersion(p) : p.version
38
42
 
@@ -44,6 +48,9 @@ export function PluginCard({ plugin: p, copied, installedName, installedVersion,
44
48
  versionShown ? h('span', { className: styles.versionBadge, title: t('version') }, versionShown) : null,
45
49
  // 已安装且有更新:醒目提示(有 release 比版本号、无 release 比仓库最近更新时间)
46
50
  update ? h('span', { className: styles.updateBadge, title: t('updateAvailableHint') }, t('updateAvailable')) : null,
51
+ pendingUntil !== null
52
+ ? h('span', { className: styles.updateBadge, title: t('updatePendingHint') }, t('updatePending', { time: clockTime(pendingUntil) }))
53
+ : null,
47
54
  p.category ? h('span', { className: styles.categoryBadge }, categoryLabel(CATEGORY_LABELS, p.category, langKey)) : null,
48
55
  p.compatibility?.status === 'verified'
49
56
  ? h('span', { className: styles.verified }, t('verified'))
@@ -14,7 +14,7 @@ import type { LocaleId, Translate } from '../../types.ts'
14
14
  import type { InstalledItem } from '../../logic/installed.ts'
15
15
  import { CATEGORY_LABELS, categoryLabel } from '../../logic/constants.ts'
16
16
  import { pluginDetailUrl } from '../../logic/urls.ts'
17
- import { fmtStars } from '../../logic/format.ts'
17
+ import { clockTime, fmtStars } from '../../logic/format.ts'
18
18
  import { CloseIcon, CopyIcon, FolderIcon, LinkIcon } from '../ui/icons.tsx'
19
19
 
20
20
  /** ISO 安装时间 → 本地可读;非法值返回 null(行内不显示)。 */
@@ -69,7 +69,10 @@ export function InstalledDetailModal({ item, t, lang, langPath, onClose, onUpdat
69
69
  item.installedVersion && item.catalogVersion
70
70
  ? t('versionUpHint', { from: item.installedVersion, to: item.catalogVersion })
71
71
  : t('updateAvailableHint'))
72
- : null,
72
+ : item.pendingUntil != null
73
+ // 门禁暂缓:给确切可重试时刻,而不是让「有更新」静默消失
74
+ ? h('div', { className: styles.detailUpdateHint }, t('updatePending', { time: clockTime(item.pendingUntil) }))
75
+ : null,
73
76
  // 详情信息网格:分类 / 状态 / 版本 / 收录入口 / 包名 / 统计 / 安装路径 / 安装时间 / 最后更新
74
77
  h('div', { className: styles.detailGrid },
75
78
  p?.category
@@ -17,6 +17,7 @@ import styles from '../../styles/InstalledView.module.css'
17
17
  import type { LocaleId, Translate } from '../../types.ts'
18
18
  import type { InstalledItem } from '../../logic/installed.ts'
19
19
  import { CATEGORY_LABELS, categoryLabel } from '../../logic/constants.ts'
20
+ import { clockTime } from '../../logic/format.ts'
20
21
  import { SortArrowIcon } from '../ui/icons.tsx'
21
22
 
22
23
  const INSTALLED_SORTS = ['sortName', 'sortInstalledAt', 'sortStars', 'sortForks'] as const
@@ -108,7 +109,10 @@ function InstalledRow({ item, t, langKey, canReveal, revealLabel, pendingRestart
108
109
  : null,
109
110
  item.hasUpdate
110
111
  ? h('span', { className: styles.updateBadge, title: t('updateAvailableHint') }, t('updateAvailable'))
111
- : null,
112
+ : item.pendingUntil != null
113
+ // 门禁暂缓:更新存在但还装不上,给确切时刻,避免看起来「已是最新」
114
+ ? h('span', { className: styles.updateBadge, title: t('updatePendingHint') }, t('updatePending', { time: clockTime(item.pendingUntil) }))
115
+ : null,
112
116
  ),
113
117
  // 来源标签列(行尾、操作按钮前,右对齐固定):目录插件 →「插件市场」(品牌色),
114
118
  // 命令行安装 →「手动安装」(琥珀色);两词 4 字等宽,所有条目标签同列对齐
@@ -59,6 +59,7 @@ export function MarketView({ catalog, t, langPath, langKey, copied, resultText,
59
59
  installedName: catalog.installedName,
60
60
  installedVersion: catalog.installedVersion,
61
61
  hasUpdate: catalog.hasUpdate,
62
+ updatePendingUntil: catalog.updatePendingUntil,
62
63
  langKey,
63
64
  onInstall,
64
65
  onUninstall,
@@ -16,7 +16,7 @@ import type { SortKey } from '../logic/constants.ts'
16
16
  import { fetchCatalog, fetchStats } from '../data/catalog.ts'
17
17
  import { fetchHubAbout, fetchHubUpdate } from '../data/hub.ts'
18
18
  import { fetchInstalled, fetchNpmReleaseAge, type NpmReleaseInfo } from '../data/host.ts'
19
- import { isReleaseTooFresh, isUpdateOffered, pickHubUpdate } from '../logic/release-age.ts'
19
+ import { isReleaseTooFresh, pickHubUpdate, updateDecision } from '../logic/release-age.ts'
20
20
  import {
21
21
  hasUpdateOf, installedItemsOf, installedNameOf, installedVersionOf, npmPackageOf,
22
22
  } from '../logic/installed.ts'
@@ -248,16 +248,24 @@ export function useCatalog(lang: LocaleId) {
248
248
  * - npm 上最新版并不高于已装版本 —— 更新走的是 `pkg@latest`,此时无论目录标到哪个 tag,
249
249
  * 都不可能装上(作者可能只打了 GitHub release 没发 npm),提示只会变成永远点不掉的循环。
250
250
  */
251
- const updateBlocked = useMemo(() => {
252
- const blocked = new Set<string>()
253
- if (!releaseAgeReady) return blocked
251
+ const updateDecisions = useMemo(() => {
252
+ const map = new Map<string, { offered: boolean; pendingUntil: number | null }>()
253
+ if (!releaseAgeReady) return map
254
254
  const now = Date.now()
255
255
  for (const pkg of releaseAgeCandidates) {
256
- if (!isUpdateOffered(releaseAge.releases[pkg] ?? null, installedByPackage.get(pkg) ?? null, now)) blocked.add(pkg)
256
+ map.set(pkg, updateDecision(releaseAge.releases[pkg] ?? null, installedByPackage.get(pkg) ?? null, now))
257
257
  }
258
- return blocked
258
+ return map
259
259
  }, [releaseAgeReady, releaseAgeCandidates, releaseAge.releases, installedByPackage])
260
260
 
261
+ const updateBlocked = useMemo(() => {
262
+ const blocked = new Set<string>()
263
+ for (const [pkg, decision] of updateDecisions) {
264
+ if (!decision.offered) blocked.add(pkg)
265
+ }
266
+ return blocked
267
+ }, [updateDecisions])
268
+
261
269
  /**
262
270
  * 是否有更新:仅对已安装插件有意义。
263
271
  * 双信号判定——有 release 版本的比版本;无版本(repo 不打 tag)的比仓库最近更新时间
@@ -267,18 +275,33 @@ export function useCatalog(lang: LocaleId) {
267
275
  const hasUpdate = (p: HubPlugin): boolean => {
268
276
  if (!hasUpdateOf(p, installed, versions)) return false
269
277
  const pkg = npmPackageOf(p, versions)
270
- return pkg === null || !updateBlocked.has(pkg)
278
+ if (pkg === null) return true
279
+ // 复核没回来时先不亮:否则会出现「先亮有更新、几秒后又撤掉」
280
+ return releaseAgeReady && !updateBlocked.has(pkg)
281
+ }
282
+
283
+ /** 更新被门禁暂缓时的可重试时刻(毫秒);未安装/无更新/非门禁原因 → null。 */
284
+ const updatePendingUntil = (p: HubPlugin): number | null => {
285
+ if (!hasUpdateOf(p, installed, versions)) return null
286
+ const pkg = npmPackageOf(p, versions)
287
+ if (pkg === null) return null
288
+ return updateDecisions.get(pkg)?.pendingUntil ?? null
271
289
  }
272
290
 
273
- /** 已安装项统一列表(目录元数据 + 运行时信息合并):被拦下的去掉更新标记,驱动「已安装」tab。 */
291
+ /** 已安装项统一列表(目录元数据 + 运行时信息合并):校验未就绪/被拦下的去掉更新标记、
292
+ * 门禁暂缓的带上可重试时刻,驱动「已安装」tab。 */
274
293
  const installedItems = useMemo<InstalledItem[]>(() => {
275
- if (updateBlocked.size === 0) return rawInstalledItems
276
294
  return rawInstalledItems.map((item) => {
277
295
  if (!item.hasUpdate || !item.plugin) return item
278
296
  const pkg = npmPackageOf(item.plugin, versions)
279
- return pkg !== null && updateBlocked.has(pkg) ? { ...item, hasUpdate: false } : item
297
+ if (pkg === null) return item
298
+ if (!releaseAgeReady) return { ...item, hasUpdate: false }
299
+ const decision = updateDecisions.get(pkg)
300
+ return decision === undefined || decision.offered
301
+ ? item
302
+ : { ...item, hasUpdate: false, pendingUntil: decision.pendingUntil }
280
303
  })
281
- }, [rawInstalledItems, updateBlocked, versions])
304
+ }, [rawInstalledItems, releaseAgeReady, updateDecisions, versions])
282
305
 
283
306
  /**
284
307
  * Hub 自身更新状态:Hub 就是当前运行的应用(始终已安装,无需 installedName 命中)。
@@ -353,6 +376,7 @@ export function useCatalog(lang: LocaleId) {
353
376
  installedName,
354
377
  installedVersion,
355
378
  hasUpdate,
379
+ updatePendingUntil,
356
380
  hubHasUpdate,
357
381
  hubUpdateState,
358
382
  hubUpdateTarget,
@@ -482,6 +482,10 @@ export const zh = {
482
482
  update: '可更新',
483
483
  updateAvailable: '有更新',
484
484
  updateAvailableHint: '检测到更新:有版本号的仓库比对版本号,无版本号的仓库比对仓库最近更新时间',
485
+ // 门禁暂缓:新版本发布未满 24 小时,此时安装会被 pnpm 静默回退(或直接拒绝),
486
+ // 给出确切可重试时刻,而不是让徽标静默消失、让用户以为已经是最新
487
+ updatePending: '暂缓到 {time} 后可更新',
488
+ updatePendingHint: '新版本发布未满 24 小时,pnpm 供应链门禁会拦下;到时间后再更新即可',
485
489
  hubUpdateHint: '检测到 DSH Plugin Hub 新版本,点击查看更新内容并更新',
486
490
  hubUpdateTitle: 'DSH Plugin Hub 有新版本',
487
491
  hubUpdateDesc: 'v{version} 已发布,点击下方按钮即可更新,完成后需重启宿主生效。',
@@ -977,6 +981,8 @@ export const en = {
977
981
  update: 'Update',
978
982
  updateAvailable: 'Update available',
979
983
  updateAvailableHint: 'An update was detected: repos with a release are compared by tag, repos without one by last push time',
984
+ updatePending: 'Update available after {time}',
985
+ updatePendingHint: 'The new release is less than 24 hours old, so the pnpm supply-chain hold rejects it until then',
980
986
  hubUpdateHint: 'A new DSH Plugin Hub version is available — click to view the changelog and update',
981
987
  hubUpdateTitle: 'A new DSH Plugin Hub version is available',
982
988
  hubUpdateDesc: 'v{version} has been released — click below to update, then restart the host to apply it.',
@@ -24,3 +24,13 @@ export function relTime(iso: string | undefined, t: Translate): string {
24
24
  if (days < 365) return t('monthsAgo', { months: Math.floor(days / 30) })
25
25
  return t('yearsAgo', { years: Math.floor(days / 365) })
26
26
  }
27
+
28
+ /** 具体时刻:当天只给「时:分」,跨天再补「月/日」,用于「暂缓到何时可更新」。 */
29
+ export function clockTime(ms: number): string {
30
+ const at = new Date(ms)
31
+ const now = new Date()
32
+ const hh = String(at.getHours()).padStart(2, '0')
33
+ const mm = String(at.getMinutes()).padStart(2, '0')
34
+ const sameDay = at.getFullYear() === now.getFullYear() && at.getMonth() === now.getMonth() && at.getDate() === now.getDate()
35
+ return sameDay ? `${hh}:${mm}` : `${at.getMonth() + 1}/${at.getDate()} ${hh}:${mm}`
36
+ }
@@ -42,6 +42,8 @@ export interface InstalledItem {
42
42
  catalogVersion: string | null
43
43
  /** 是否有可用更新(有版本比版本,无版本比仓库更新时间) */
44
44
  hasUpdate: boolean
45
+ /** 更新被供应链门禁暂缓时的可重试时刻(毫秒);无更新/非门禁原因 → null */
46
+ pendingUntil: number | null
45
47
  /** 是否已加载进运行中 loader(官方 ctx.loader 对账);false = 已安装但未挂载(装完未重启) */
46
48
  loaded: boolean
47
49
  /** 是否真正的 dsh 插件(包内声明 dsh 配置 / 在 profile bundles 清单):
@@ -230,6 +232,7 @@ export function installedItemsOf(
230
232
  installedAt: rec?.installedAt ?? null,
231
233
  catalogVersion: p.version ?? null,
232
234
  hasUpdate: hasUpdateOf(p, installed, versions),
235
+ pendingUntil: null,
233
236
  loaded: loadedNames?.includes(name) ?? false,
234
237
  dshCapable: dshCapableNames?.includes(name) ?? false,
235
238
  })
@@ -254,6 +257,7 @@ export function installedItemsOf(
254
257
  catalogVersion: null,
255
258
  // 目录外没有目录信号可比,无法判断是否有更新
256
259
  hasUpdate: false,
260
+ pendingUntil: null,
257
261
  loaded: loadedNames?.includes(name) ?? false,
258
262
  dshCapable: dshCapableNames?.includes(name) ?? false,
259
263
  })
@@ -61,23 +61,44 @@ export function compareVersions(a: string | null | undefined, b: string | null |
61
61
  return 0
62
62
  }
63
63
 
64
+ /** 「可更新」判定结果:是否提示,以及被供应链门禁拦下时的到期时刻。 */
65
+ export interface UpdateDecision {
66
+ offered: boolean
67
+ /** 发布未满门槛 → 可重试时刻(毫秒);非门禁原因(npm 上没有更高版本)→ null */
68
+ pendingUntil: number | null
69
+ }
70
+
64
71
  /**
65
72
  * 是否值得提示「可更新」:npm 上最新可安装版本确实比已装版本新,且已过供应链门禁。
66
73
  *
67
74
  * 更新命令执行的是 `pkg@latest`,所以「目录里标的版本」不是最终能装上的版本:
68
75
  * 作者可能只打了 GitHub release 没发 npm —— 此时提示会变成永远点不掉的循环。
69
- * 任一信息缺失(查不到发布信息 / 没有已装版本记录)返回 true,交回调用方原有判定:
76
+ * 任一信息缺失(查不到发布信息 / 没有已装版本记录)返回 offered,交回调用方原有判定:
70
77
  * 宁可提示,也不因为一次查询失败把真实更新吃掉。
71
78
  */
79
+ export function updateDecision(
80
+ release: { version: string; publishedAt: string } | null,
81
+ installedVersion: string | null,
82
+ now: number,
83
+ ): UpdateDecision {
84
+ if (release === null) return { offered: true, pendingUntil: null }
85
+ if (isReleaseTooFresh(release.publishedAt, now)) {
86
+ const ms = Date.parse(release.publishedAt)
87
+ return { offered: false, pendingUntil: Number.isNaN(ms) ? null : ms + MIN_RELEASE_AGE_MS }
88
+ }
89
+ if (installedVersion !== null && compareVersions(release.version, installedVersion) <= 0) {
90
+ return { offered: false, pendingUntil: null }
91
+ }
92
+ return { offered: true, pendingUntil: null }
93
+ }
94
+
95
+ /** 是否提示「可更新」(只要布尔结果的场景)。 */
72
96
  export function isUpdateOffered(
73
97
  release: { version: string; publishedAt: string } | null,
74
98
  installedVersion: string | null,
75
99
  now: number,
76
100
  ): boolean {
77
- if (release === null) return true
78
- if (isReleaseTooFresh(release.publishedAt, now)) return false
79
- if (installedVersion !== null && compareVersions(release.version, installedVersion) <= 0) return false
80
- return true
101
+ return updateDecision(release, installedVersion, now).offered
81
102
  }
82
103
 
83
104
  /**