dsh-plugin 2.2.2 → 2.2.4

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.
@@ -18,7 +18,7 @@ import { backfillOperationLog, createOperationLogTap, hasSubstantiveOutput, oper
18
18
  import { cleanLine, estimateProgress } from '../profile/progress.js';
19
19
  import { hasClientUi, isDshPlugin, mountLoadedEntry, removeLoadedEntry } from '../loader.js';
20
20
  import { addPendingRestart, clearPendingRestart } from '../profile/pending-restart.js';
21
- import { addAllowBuildsKey, bundleIncludes, githubRepoOf, parseAllowBuildsKey, profileDirectory } from '../profile/profile.js';
21
+ import { addAllowBuildsKey, bundleIncludes, githubRepoOf, parseAllowBuildsKey, profileDirectory, validPackageName } from '../profile/profile.js';
22
22
  import { appendLog } from '../log.js';
23
23
  import { commandOnPath, gitMissingDetail } from './preflight.js';
24
24
  import { isTransportFailure, registryHost } from './registry-fallback.js';
@@ -200,14 +200,17 @@ export function desktopCliBootstrap(entry) {
200
200
  }
201
201
  /**
202
202
  * 传给 CLI 的安装目标:钉住版本优先(Hub 自我更新提示的是已过供应链门禁的版本),
203
- * 其次更新走 `@latest`,git 源与全局 npm 安装保持原样。
203
+ * 其次 npm 的 add/update 都显式 `@latest`;git 源、全局安装、已带版本/tag 的 spec 保持原样。
204
204
  */
205
205
  export function npmInstallTargetOf(target, opts) {
206
206
  if (opts.global || githubRepoOf(target) !== null)
207
207
  return target;
208
208
  if (opts.pinned !== undefined && opts.pinned !== '')
209
209
  return opts.pinned;
210
- return opts.update ? `${target}@latest` : target;
210
+ // 裸包名才补 @latest:pnpm 对已存在的精确 spec 是幂等的,不显式要求就会留用旧版本
211
+ if (!(opts.update || opts.add) || !validPackageName(target))
212
+ return target;
213
+ return `${target}@latest`;
211
214
  }
212
215
  function stopChild(child) {
213
216
  if (process.platform === 'win32' && child.pid !== undefined) {
@@ -372,15 +375,13 @@ function spawnMutation(options) {
372
375
  && process.versions.electron !== undefined) {
373
376
  pushLine(task, '[desktop-cli-fallback] running inside Electron but no desktop CLI bootstrap was found — falling back to dsh on PATH (a desktop-managed profile will be rejected)');
374
377
  }
375
- // 更新已安装的 npm 包:显式指定 @latest。pnpm add <pkg>(无版本)对已存在依赖
376
- // (尤其精确 spec 如 "1.3.0")是幂等的 —— 输出 "Already up to date" 且不改 package.json,
377
- // 导致「更新成功」但实际仍停留在旧版本;git 地址与全局安装不受此影响。
378
- // update 动作实际执行 `add <target>@latest`(pnpm 对已存在依赖带显式版本即原位覆盖升级),
379
- // CLI 动词仍透传 add —— 不执行官方 update 动词,避免与既有覆盖重装机制分叉。
378
+ // 安装/更新 npm 包都显式 @latest:pnpm add <pkg>(无版本)对已存在的精确 spec 是幂等的,
379
+ // 会原样留用旧版本(更新不生效,普通安装也可能装回不支持当前宿主的旧包)。
380
+ // update 动作同样执行 `add <target>@latest`,CLI 动词透传 add,不与官方 update 动词分叉。
380
381
  const isUpdate = action === 'update' || updateNpm === true;
381
382
  const cliAction = action === 'update' ? 'add' : action;
382
383
  // 钉住版本优先于 @latest:提示的是「已过门禁」的版本,装 latest 会被 pnpm 拒绝
383
- const npmUpdateTarget = npmInstallTargetOf(target, { update: isUpdate, global: isGlobalNpm, pinned: installSpec });
384
+ const npmUpdateTarget = npmInstallTargetOf(target, { update: isUpdate, global: isGlobalNpm, pinned: installSpec, add: cliAction === 'add' });
384
385
  const args = isGlobalNpm
385
386
  ? ['install', '-g', ...(globalNpm ?? [])]
386
387
  : [...invocation.prefixArgs, 'plugin', '--profile', profile, cliAction, npmUpdateTarget];
@@ -40,12 +40,13 @@ export declare function cancelTask(id: number): boolean;
40
40
  export declare function desktopCliBootstrap(entry: string): string | null;
41
41
  /**
42
42
  * 传给 CLI 的安装目标:钉住版本优先(Hub 自我更新提示的是已过供应链门禁的版本),
43
- * 其次更新走 `@latest`,git 源与全局 npm 安装保持原样。
43
+ * 其次 npm 的 add/update 都显式 `@latest`;git 源、全局安装、已带版本/tag 的 spec 保持原样。
44
44
  */
45
45
  export declare function npmInstallTargetOf(target: string, opts: {
46
46
  update: boolean;
47
47
  global: boolean;
48
48
  pinned?: string;
49
+ add?: boolean;
49
50
  }): string;
50
51
  /**
51
52
  * 插件变更入队并返回其任务。任务严格串行:队列 worker 只在上一个结束后才启动下一个。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-plugin",
3
- "version": "2.2.2",
3
+ "version": "2.2.4",
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",
@@ -6,7 +6,7 @@
6
6
  * Hub 元数据仓库:自我更新检查与「关注我们」内容背后的 Cloudflare Worker 接口。
7
7
  * 失败降级为 null —— 由调用方决定如何兜底(不显示徽标、占位文案),绝不抛错。
8
8
  */
9
- import type { HubAboutInfo, HubUpdateInfo } from '../types.ts'
9
+ import type { HubAboutInfo, HubUpdateInfo, HubUpdateVersion } from '../types.ts'
10
10
  import { HUB_ABOUT_URL, HUB_UPDATE_URL } from '../logic/constants.ts'
11
11
 
12
12
  /** 版本接口是 Pages 静态文件,默认带 CDN 缓存(Cache-Control: public, max-age=43200,
@@ -18,7 +18,26 @@ function busted(url: string): string {
18
18
  return `${url}${sep}_t=${Date.now()}`
19
19
  }
20
20
 
21
- /** Worker 版本控制中心:最新版本 + Markdown 变更记录;不可用返回 null。 */
21
+ /** 归一化接口下发的 recentVersions:只保留带版本号的条目,其余字段按类型补。
22
+ * 这条数据是「最新版还在 24.5h 门禁内时回退到更早的已过门槛版本」的唯一依据;
23
+ * 入口处漏传会让候选只剩最新版,整体变成 pending —— 用户就永远看不到更新入口。 */
24
+ function recentVersionsOf(value: unknown): HubUpdateVersion[] {
25
+ if (!Array.isArray(value)) return []
26
+ const list: HubUpdateVersion[] = []
27
+ for (const item of value) {
28
+ if (item === null || typeof item !== 'object') continue
29
+ const entry = item as { version?: unknown; publishedAt?: unknown; notes?: unknown }
30
+ if (typeof entry.version !== 'string' || entry.version === '') continue
31
+ list.push({
32
+ version: entry.version,
33
+ ...(typeof entry.publishedAt === 'string' ? { publishedAt: entry.publishedAt } : {}),
34
+ ...(entry.notes === undefined ? {} : { notes: entry.notes as HubUpdateVersion['notes'] }),
35
+ })
36
+ }
37
+ return list
38
+ }
39
+
40
+ /** 版本控制中心:最新版本 + Markdown 变更记录 + 最近发版(门禁回退用);不可用返回 null。 */
22
41
  export async function fetchHubUpdate(): Promise<HubUpdateInfo | null> {
23
42
  try {
24
43
  const res = await fetch(busted(HUB_UPDATE_URL), { cache: 'no-store' })
@@ -27,7 +46,12 @@ export async function fetchHubUpdate(): Promise<HubUpdateInfo | null> {
27
46
  if (!data) return null
28
47
  const version = typeof data.version === 'string' && data.version.length > 0 ? data.version : null
29
48
  if (version === null) return null
30
- return { version, publishedAt: data.publishedAt ?? null, notes: data.notes ?? null }
49
+ return {
50
+ version,
51
+ publishedAt: data.publishedAt ?? null,
52
+ notes: data.notes ?? null,
53
+ recentVersions: recentVersionsOf(data.recentVersions),
54
+ }
31
55
  } catch {
32
56
  return null
33
57
  }
@@ -20,7 +20,7 @@ import { cleanLine, estimateProgress } from '../profile/progress.ts'
20
20
  import type { LoaderHandle } from '../loader.ts'
21
21
  import { hasClientUi, isDshPlugin, mountLoadedEntry, removeLoadedEntry } from '../loader.ts'
22
22
  import { addPendingRestart, clearPendingRestart } from '../profile/pending-restart.ts'
23
- import { addAllowBuildsKey, bundleIncludes, githubRepoOf, parseAllowBuildsKey, profileDirectory } from '../profile/profile.ts'
23
+ import { addAllowBuildsKey, bundleIncludes, githubRepoOf, parseAllowBuildsKey, profileDirectory, validPackageName } from '../profile/profile.ts'
24
24
  import { appendLog } from '../log.ts'
25
25
  import { commandOnPath, gitMissingDetail } from './preflight.ts'
26
26
  import { isTransportFailure, registryHost } from './registry-fallback.ts'
@@ -218,12 +218,14 @@ export function desktopCliBootstrap(entry: string): string | null {
218
218
 
219
219
  /**
220
220
  * 传给 CLI 的安装目标:钉住版本优先(Hub 自我更新提示的是已过供应链门禁的版本),
221
- * 其次更新走 `@latest`,git 源与全局 npm 安装保持原样。
221
+ * 其次 npm 的 add/update 都显式 `@latest`;git 源、全局安装、已带版本/tag 的 spec 保持原样。
222
222
  */
223
- export function npmInstallTargetOf(target: string, opts: { update: boolean; global: boolean; pinned?: string }): string {
223
+ export function npmInstallTargetOf(target: string, opts: { update: boolean; global: boolean; pinned?: string; add?: boolean }): string {
224
224
  if (opts.global || githubRepoOf(target) !== null) return target
225
225
  if (opts.pinned !== undefined && opts.pinned !== '') return opts.pinned
226
- return opts.update ? `${target}@latest` : target
226
+ // 裸包名才补 @latest:pnpm 对已存在的精确 spec 是幂等的,不显式要求就会留用旧版本
227
+ if (!(opts.update || opts.add) || !validPackageName(target)) return target
228
+ return `${target}@latest`
227
229
  }
228
230
 
229
231
  function stopChild(child: ReturnType<typeof spawn>): void {
@@ -432,15 +434,13 @@ function spawnMutation(options: {
432
434
  && (process.versions as Record<string, string | undefined>).electron !== undefined) {
433
435
  pushLine(task, '[desktop-cli-fallback] running inside Electron but no desktop CLI bootstrap was found — falling back to dsh on PATH (a desktop-managed profile will be rejected)')
434
436
  }
435
- // 更新已安装的 npm 包:显式指定 @latest。pnpm add <pkg>(无版本)对已存在依赖
436
- // (尤其精确 spec 如 "1.3.0")是幂等的 —— 输出 "Already up to date" 且不改 package.json,
437
- // 导致「更新成功」但实际仍停留在旧版本;git 地址与全局安装不受此影响。
438
- // update 动作实际执行 `add <target>@latest`(pnpm 对已存在依赖带显式版本即原位覆盖升级),
439
- // CLI 动词仍透传 add —— 不执行官方 update 动词,避免与既有覆盖重装机制分叉。
437
+ // 安装/更新 npm 包都显式 @latest:pnpm add <pkg>(无版本)对已存在的精确 spec 是幂等的,
438
+ // 会原样留用旧版本(更新不生效,普通安装也可能装回不支持当前宿主的旧包)。
439
+ // update 动作同样执行 `add <target>@latest`,CLI 动词透传 add,不与官方 update 动词分叉。
440
440
  const isUpdate = action === 'update' || updateNpm === true
441
441
  const cliAction = action === 'update' ? 'add' : action
442
442
  // 钉住版本优先于 @latest:提示的是「已过门禁」的版本,装 latest 会被 pnpm 拒绝
443
- const npmUpdateTarget = npmInstallTargetOf(target, { update: isUpdate, global: isGlobalNpm, pinned: installSpec })
443
+ const npmUpdateTarget = npmInstallTargetOf(target, { update: isUpdate, global: isGlobalNpm, pinned: installSpec, add: cliAction === 'add' })
444
444
  const args = isGlobalNpm
445
445
  ? ['install', '-g', ...(globalNpm ?? [])]
446
446
  : [...invocation.prefixArgs, 'plugin', '--profile', profile, cliAction, npmUpdateTarget]