dsh-plugin 2.2.1 → 2.2.3
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/client/client.js +327 -306
- package/client/client.js.map +1 -1
- package/lib/services/profile/installed-versions.js +26 -2
- package/lib/types/services/profile/installed-versions.d.ts +3 -0
- package/package.json +1 -1
- package/src/client/data/hub.ts +27 -3
- package/src/client/logic/installed.ts +9 -5
- package/src/client/logic/release-age.ts +5 -5
- package/src/server/services/profile/installed-versions.ts +24 -2
|
@@ -23,10 +23,27 @@ import { dirname, join } from 'node:path';
|
|
|
23
23
|
export function versionsFilePath(profile) {
|
|
24
24
|
return join(process.env.DSH_HOME ?? join(homedir(), '.dsh'), 'profiles', profile, 'gro.ngilp-hsd-versions.json');
|
|
25
25
|
}
|
|
26
|
+
/** 读取某 profile 的版本账本;缺失/损坏/字段不全一律归一化成完整形状。
|
|
27
|
+
* 安装一开始只会写入 npm 包名映射(版本要等任务结束才知道),所以磁盘上可能存在
|
|
28
|
+
* 只有 npmPackage 的记录 —— 原样下发会让客户端的版本比较拿到 undefined 直接抛错。 */
|
|
26
29
|
export function readInstalledVersions(profile) {
|
|
27
30
|
try {
|
|
28
31
|
const raw = JSON.parse(readFileSync(versionsFilePath(profile), 'utf8'));
|
|
29
|
-
|
|
32
|
+
if (raw === null || typeof raw !== 'object' || Array.isArray(raw))
|
|
33
|
+
return {};
|
|
34
|
+
const table = {};
|
|
35
|
+
for (const [key, value] of Object.entries(raw)) {
|
|
36
|
+
if (value === null || typeof value !== 'object')
|
|
37
|
+
continue;
|
|
38
|
+
const rec = value;
|
|
39
|
+
table[key] = {
|
|
40
|
+
version: typeof rec.version === 'string' ? rec.version : '',
|
|
41
|
+
updatedAt: typeof rec.updatedAt === 'string' ? rec.updatedAt : '',
|
|
42
|
+
installedAt: typeof rec.installedAt === 'string' ? rec.installedAt : '',
|
|
43
|
+
...(typeof rec.npmPackage === 'string' && rec.npmPackage !== '' ? { npmPackage: rec.npmPackage } : {}),
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
return table;
|
|
30
47
|
}
|
|
31
48
|
catch {
|
|
32
49
|
return {};
|
|
@@ -48,7 +65,14 @@ export function recordInstalledVersion(profile, repo, version, updatedAt) {
|
|
|
48
65
|
export function recordResolvedNpmPackage(profile, repo, npmPackage) {
|
|
49
66
|
const table = readInstalledVersions(profile);
|
|
50
67
|
const key = repo.toLowerCase();
|
|
51
|
-
|
|
68
|
+
// 显式写全字段:这一步发生在安装开始时,版本还不知道 —— 但落盘记录必须是完整形状,
|
|
69
|
+
// 否则只带 npmPackage 的残缺记录会在客户端版本比较时崩掉整块界面
|
|
70
|
+
table[key] = {
|
|
71
|
+
version: table[key]?.version ?? '',
|
|
72
|
+
updatedAt: table[key]?.updatedAt ?? '',
|
|
73
|
+
installedAt: table[key]?.installedAt ?? '',
|
|
74
|
+
npmPackage,
|
|
75
|
+
};
|
|
52
76
|
writeVersions(profile, table);
|
|
53
77
|
}
|
|
54
78
|
/** 清除某仓库记录的版本(卸载时调用)。 */
|
|
@@ -10,6 +10,9 @@ export interface InstalledVersionRecord {
|
|
|
10
10
|
}
|
|
11
11
|
export type InstalledVersionTable = Record<string, InstalledVersionRecord>;
|
|
12
12
|
export declare function versionsFilePath(profile: string): string;
|
|
13
|
+
/** 读取某 profile 的版本账本;缺失/损坏/字段不全一律归一化成完整形状。
|
|
14
|
+
* 安装一开始只会写入 npm 包名映射(版本要等任务结束才知道),所以磁盘上可能存在
|
|
15
|
+
* 只有 npmPackage 的记录 —— 原样下发会让客户端的版本比较拿到 undefined 直接抛错。 */
|
|
13
16
|
export declare function readInstalledVersions(profile: string): InstalledVersionTable;
|
|
14
17
|
/** 记录某仓库安装时的目录信号(版本 + 仓库更新时间)。 */
|
|
15
18
|
export declare function recordInstalledVersion(profile: string, repo: string, version: string, updatedAt: string): void;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-plugin",
|
|
3
|
-
"version": "2.2.
|
|
3
|
+
"version": "2.2.3",
|
|
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",
|
package/src/client/data/hub.ts
CHANGED
|
@@ -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
|
-
/**
|
|
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 {
|
|
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
|
}
|
|
@@ -91,7 +91,7 @@ export function installedNameOf(
|
|
|
91
91
|
export function installedVersionOf(plugin: HubPlugin, versions: Record<string, InstalledVersionSignal>): string | null {
|
|
92
92
|
const repo = plugin.source?.repo
|
|
93
93
|
if (!repo) return null
|
|
94
|
-
return versions
|
|
94
|
+
return signalOf(repo, versions)?.version || null
|
|
95
95
|
}
|
|
96
96
|
|
|
97
97
|
/** 该插件对应的 npm 包名(目录下发,或安装时持久化的映射);无则 null。决定更新是否走 npm registry。 */
|
|
@@ -118,9 +118,13 @@ export function hasUpdateOf(
|
|
|
118
118
|
if (!repo) return false
|
|
119
119
|
const rec = versions[repo.toLowerCase()]
|
|
120
120
|
if (!rec) return false
|
|
121
|
-
|
|
121
|
+
// 账本记录可能是残缺的:安装一开始就会先写入 npm 包名映射,版本要等任务结束才知道。
|
|
122
|
+
// 缺版本的记录按「无版本信号」处理、退回更新时间比较 —— 直接拿去比版本会让整个插件区块崩掉
|
|
123
|
+
const recorded = typeof rec.version === 'string' ? rec.version : ''
|
|
124
|
+
if (plugin.version && recorded !== '') return compareVersions(plugin.version, recorded) > 0
|
|
122
125
|
const current = plugin.dates?.repoUpdatedAt
|
|
123
|
-
|
|
126
|
+
const seen = typeof rec.updatedAt === 'string' ? rec.updatedAt : ''
|
|
127
|
+
return Boolean(current && seen && current > seen)
|
|
124
128
|
}
|
|
125
129
|
|
|
126
130
|
/** 依赖 spec 能否解析出仓库身份(owner/repo 形态才算)。 */
|
|
@@ -222,7 +226,7 @@ export function installedItemsOf(
|
|
|
222
226
|
plugin: p,
|
|
223
227
|
repo,
|
|
224
228
|
installPath: paths?.[name] ?? null,
|
|
225
|
-
installedVersion: rec?.version
|
|
229
|
+
installedVersion: rec?.version || null,
|
|
226
230
|
installedAt: rec?.installedAt ?? null,
|
|
227
231
|
catalogVersion: p.version ?? null,
|
|
228
232
|
hasUpdate: hasUpdateOf(p, installed, versions),
|
|
@@ -245,7 +249,7 @@ export function installedItemsOf(
|
|
|
245
249
|
plugin: null,
|
|
246
250
|
repo,
|
|
247
251
|
installPath: paths?.[name] ?? null,
|
|
248
|
-
installedVersion: rec?.version
|
|
252
|
+
installedVersion: rec?.version || null,
|
|
249
253
|
installedAt: rec?.installedAt ?? null,
|
|
250
254
|
catalogVersion: null,
|
|
251
255
|
// 目录外没有目录信号可比,无法判断是否有更新
|
|
@@ -23,14 +23,14 @@ export function isReleaseTooFresh(publishedAt: string | null | undefined, now: n
|
|
|
23
23
|
* 兼容目录里的 `v1.1.8` 与 npm 的 `1.1.8`:每段先剥掉前导非数字,再取整。
|
|
24
24
|
* 预发布后缀单独处理:`1.0.0` 高于 `1.0.0-beta.1`,两个预发布版按标识符比较
|
|
25
25
|
* (纯数字段按数值,数字段低于字母段)。 */
|
|
26
|
-
export function compareVersions(a: string, b: string): number {
|
|
27
|
-
const core = (v: string): number[] =>
|
|
28
|
-
(v.split('+')[0]
|
|
26
|
+
export function compareVersions(a: string | null | undefined, b: string | null | undefined): number {
|
|
27
|
+
const core = (v: string | null | undefined): number[] =>
|
|
28
|
+
(v ?? '').split('+')[0]!.split('-')[0]!
|
|
29
29
|
.split('.')
|
|
30
30
|
.map((part) => Number.parseInt(part.replace(/^[^\d]+/, ''), 10))
|
|
31
31
|
.filter((n) => !Number.isNaN(n))
|
|
32
|
-
const pre = (v: string): string[] => {
|
|
33
|
-
const rest = (v.split('+')[0]
|
|
32
|
+
const pre = (v: string | null | undefined): string[] => {
|
|
33
|
+
const rest = (v ?? '').split('+')[0]!.split('-').slice(1)
|
|
34
34
|
return rest.length === 0 ? [] : rest.join('-').split('.')
|
|
35
35
|
}
|
|
36
36
|
const va = core(a)
|
|
@@ -38,10 +38,25 @@ export function versionsFilePath(profile: string): string {
|
|
|
38
38
|
return join(process.env.DSH_HOME ?? join(homedir(), '.dsh'), 'profiles', profile, 'gro.ngilp-hsd-versions.json')
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
+
/** 读取某 profile 的版本账本;缺失/损坏/字段不全一律归一化成完整形状。
|
|
42
|
+
* 安装一开始只会写入 npm 包名映射(版本要等任务结束才知道),所以磁盘上可能存在
|
|
43
|
+
* 只有 npmPackage 的记录 —— 原样下发会让客户端的版本比较拿到 undefined 直接抛错。 */
|
|
41
44
|
export function readInstalledVersions(profile: string): InstalledVersionTable {
|
|
42
45
|
try {
|
|
43
46
|
const raw = JSON.parse(readFileSync(versionsFilePath(profile), 'utf8')) as unknown
|
|
44
|
-
|
|
47
|
+
if (raw === null || typeof raw !== 'object' || Array.isArray(raw)) return {}
|
|
48
|
+
const table: InstalledVersionTable = {}
|
|
49
|
+
for (const [key, value] of Object.entries(raw as Record<string, unknown>)) {
|
|
50
|
+
if (value === null || typeof value !== 'object') continue
|
|
51
|
+
const rec = value as Partial<InstalledVersionRecord>
|
|
52
|
+
table[key] = {
|
|
53
|
+
version: typeof rec.version === 'string' ? rec.version : '',
|
|
54
|
+
updatedAt: typeof rec.updatedAt === 'string' ? rec.updatedAt : '',
|
|
55
|
+
installedAt: typeof rec.installedAt === 'string' ? rec.installedAt : '',
|
|
56
|
+
...(typeof rec.npmPackage === 'string' && rec.npmPackage !== '' ? { npmPackage: rec.npmPackage } : {}),
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return table
|
|
45
60
|
} catch {
|
|
46
61
|
return {}
|
|
47
62
|
}
|
|
@@ -64,7 +79,14 @@ export function recordInstalledVersion(profile: string, repo: string, version: s
|
|
|
64
79
|
export function recordResolvedNpmPackage(profile: string, repo: string, npmPackage: string): void {
|
|
65
80
|
const table = readInstalledVersions(profile)
|
|
66
81
|
const key = repo.toLowerCase()
|
|
67
|
-
|
|
82
|
+
// 显式写全字段:这一步发生在安装开始时,版本还不知道 —— 但落盘记录必须是完整形状,
|
|
83
|
+
// 否则只带 npmPackage 的残缺记录会在客户端版本比较时崩掉整块界面
|
|
84
|
+
table[key] = {
|
|
85
|
+
version: table[key]?.version ?? '',
|
|
86
|
+
updatedAt: table[key]?.updatedAt ?? '',
|
|
87
|
+
installedAt: table[key]?.installedAt ?? '',
|
|
88
|
+
npmPackage,
|
|
89
|
+
}
|
|
68
90
|
writeVersions(profile, table)
|
|
69
91
|
}
|
|
70
92
|
|