dsh-plugin-discovery 1.1.1 → 1.2.0
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/README.md +1 -0
- package/client.js +192 -0
- package/cordis.patch.yml +4 -0
- package/index.js +319 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -54,6 +54,7 @@ github:hjhcos/dsh-plugin-discovery
|
|
|
54
54
|
- **个性化推荐**:按已装插件偏好排序
|
|
55
55
|
- **分类浏览**:桌宠 / 主题 / IM / 记忆 / 效率 / 多媒体 / 远程 / 市场 / 多智能体
|
|
56
56
|
- **远程目录更新**:优先 Gitee raw JSON,失败再试 GitHub;按 `version` 写入 `$DSH_HOME/cache/...`
|
|
57
|
+
- **插件自更新**:启动/打开面板时检查 npm 最新版(Gitee `package.json` 兜底);有新版本可自动或一键更新
|
|
57
58
|
- 关键词搜索(全部 / npm / GitHub)与链接解析安装
|
|
58
59
|
- 安装三态:未安装 / 安装中 / 已安装
|
|
59
60
|
- 中英文界面
|
package/client.js
CHANGED
|
@@ -64,6 +64,17 @@ window.__ModuleLoader__.load({
|
|
|
64
64
|
catalogOriginGithub: "GitHub",
|
|
65
65
|
catalogOriginRemote: "远程",
|
|
66
66
|
refreshCatalog: "刷新目录",
|
|
67
|
+
selfUpdateTitle: "发现插件有新版本",
|
|
68
|
+
selfUpdateDesc: "当前 {current} → 最新 {latest}(来源 {origin})",
|
|
69
|
+
selfUpdateNow: "立即更新",
|
|
70
|
+
selfUpdateChecking: "正在检查更新…",
|
|
71
|
+
selfUpdateUpdating: "正在更新…",
|
|
72
|
+
selfUpdateLatest: "发现插件已是最新版 {version}",
|
|
73
|
+
selfUpdateDone: "已更新到 {version},请刷新页面",
|
|
74
|
+
selfUpdateFileSkip: "本地开发安装,已跳过自动更新",
|
|
75
|
+
selfUpdateOriginNpm: "npm",
|
|
76
|
+
selfUpdateOriginGitee: "Gitee",
|
|
77
|
+
selfUpdateOriginGithub: "GitHub",
|
|
67
78
|
reasonFeatured: "精选",
|
|
68
79
|
reasonRelated: "与已装相关",
|
|
69
80
|
reasonPopular: "热门",
|
|
@@ -133,6 +144,17 @@ window.__ModuleLoader__.load({
|
|
|
133
144
|
catalogOriginGithub: "GitHub",
|
|
134
145
|
catalogOriginRemote: "remote",
|
|
135
146
|
refreshCatalog: "Refresh catalog",
|
|
147
|
+
selfUpdateTitle: "Discovery plugin update available",
|
|
148
|
+
selfUpdateDesc: "Current {current} → latest {latest} (via {origin})",
|
|
149
|
+
selfUpdateNow: "Update now",
|
|
150
|
+
selfUpdateChecking: "Checking for updates…",
|
|
151
|
+
selfUpdateUpdating: "Updating…",
|
|
152
|
+
selfUpdateLatest: "Discovery plugin is up to date ({version})",
|
|
153
|
+
selfUpdateDone: "Updated to {version}. Please refresh the page.",
|
|
154
|
+
selfUpdateFileSkip: "Local file: install — auto-update skipped",
|
|
155
|
+
selfUpdateOriginNpm: "npm",
|
|
156
|
+
selfUpdateOriginGitee: "Gitee",
|
|
157
|
+
selfUpdateOriginGithub: "GitHub",
|
|
136
158
|
reasonFeatured: "Featured",
|
|
137
159
|
reasonRelated: "Related to installed",
|
|
138
160
|
reasonPopular: "Popular",
|
|
@@ -581,6 +603,27 @@ window.__ModuleLoader__.load({
|
|
|
581
603
|
color: "var(--dsw-alias-state-warn-primary, var(--dsw-alias-label-secondary))",
|
|
582
604
|
fontSize: "13px",
|
|
583
605
|
},
|
|
606
|
+
updateBox: {
|
|
607
|
+
display: "flex",
|
|
608
|
+
alignItems: "center",
|
|
609
|
+
justifyContent: "space-between",
|
|
610
|
+
gap: "12px",
|
|
611
|
+
flexWrap: "wrap",
|
|
612
|
+
padding: "12px 14px",
|
|
613
|
+
borderRadius: "var(--dsw-radius-sm, 8px)",
|
|
614
|
+
border: "1px solid color-mix(in srgb, var(--dsw-alias-state-business-primary, #4176e6) 35%, transparent)",
|
|
615
|
+
background: "color-mix(in srgb, var(--dsw-alias-state-business-primary, #4176e6) 8%, transparent)",
|
|
616
|
+
color: "var(--dsw-alias-label-primary)",
|
|
617
|
+
fontSize: "13px",
|
|
618
|
+
lineHeight: "20px",
|
|
619
|
+
},
|
|
620
|
+
updateText: {
|
|
621
|
+
display: "flex",
|
|
622
|
+
flexDirection: "column",
|
|
623
|
+
gap: "2px",
|
|
624
|
+
margin: 0,
|
|
625
|
+
flex: "1 1 220px",
|
|
626
|
+
},
|
|
584
627
|
pager: {
|
|
585
628
|
display: "flex",
|
|
586
629
|
alignItems: "center",
|
|
@@ -725,6 +768,8 @@ window.__ModuleLoader__.load({
|
|
|
725
768
|
const [category, setCategory] = React.useState("beginner");
|
|
726
769
|
const [categories, setCategories] = React.useState([]);
|
|
727
770
|
const [catalogInfo, setCatalogInfo] = React.useState({ version: 0, origin: "bundled" });
|
|
771
|
+
const [selfUpdate, setSelfUpdate] = React.useState(null);
|
|
772
|
+
const [selfUpdateBusy, setSelfUpdateBusy] = React.useState(false);
|
|
728
773
|
const [query, setQuery] = React.useState(DEFAULT_QUERY);
|
|
729
774
|
const [source, setSource] = React.useState("all");
|
|
730
775
|
const [results, setResults] = React.useState([]);
|
|
@@ -866,6 +911,10 @@ window.__ModuleLoader__.load({
|
|
|
866
911
|
version: res.catalogVersion || 0,
|
|
867
912
|
origin: res.catalogOrigin || "bundled",
|
|
868
913
|
});
|
|
914
|
+
if (res.selfUpdate) setSelfUpdate(res.selfUpdate);
|
|
915
|
+
if (res.selfUpdated && res.selfUpdate?.latestVersion) {
|
|
916
|
+
setMessage(format(t("selfUpdateDone"), { version: res.selfUpdate.latestVersion }));
|
|
917
|
+
}
|
|
869
918
|
const list = res.results || [];
|
|
870
919
|
setResults(list);
|
|
871
920
|
setPage(1);
|
|
@@ -1228,6 +1277,10 @@ window.__ModuleLoader__.load({
|
|
|
1228
1277
|
version: res.catalogVersion || 0,
|
|
1229
1278
|
origin: res.catalogOrigin || "bundled",
|
|
1230
1279
|
});
|
|
1280
|
+
if (res.selfUpdate) setSelfUpdate(res.selfUpdate);
|
|
1281
|
+
if (res.selfUpdated && res.selfUpdate?.latestVersion) {
|
|
1282
|
+
setMessage(format(t("selfUpdateDone"), { version: res.selfUpdate.latestVersion }));
|
|
1283
|
+
}
|
|
1231
1284
|
setResults(res.results || []);
|
|
1232
1285
|
setPage(1);
|
|
1233
1286
|
}
|
|
@@ -1238,6 +1291,48 @@ window.__ModuleLoader__.load({
|
|
|
1238
1291
|
})();
|
|
1239
1292
|
};
|
|
1240
1293
|
|
|
1294
|
+
const onUpdateSelf = () => {
|
|
1295
|
+
void (async () => {
|
|
1296
|
+
if (!api || typeof api.updateSelf !== "function") return;
|
|
1297
|
+
setSelfUpdateBusy(true);
|
|
1298
|
+
setError("");
|
|
1299
|
+
try {
|
|
1300
|
+
const res = await api.updateSelf({
|
|
1301
|
+
latestVersion: selfUpdate && selfUpdate.latestVersion,
|
|
1302
|
+
});
|
|
1303
|
+
if (res && res.update) setSelfUpdate(res.update);
|
|
1304
|
+
if (res && res.ok && res.updated) {
|
|
1305
|
+
setMessage(format(t("selfUpdateDone"), { version: res.version || res.update?.latestVersion }));
|
|
1306
|
+
setSelfUpdate((prev) =>
|
|
1307
|
+
prev
|
|
1308
|
+
? {
|
|
1309
|
+
...prev,
|
|
1310
|
+
updateAvailable: false,
|
|
1311
|
+
currentVersion: res.version || prev.latestVersion,
|
|
1312
|
+
latestVersion: res.version || prev.latestVersion,
|
|
1313
|
+
}
|
|
1314
|
+
: prev,
|
|
1315
|
+
);
|
|
1316
|
+
} else if (res && res.skipped) {
|
|
1317
|
+
setMessage(res.message || t("selfUpdateFileSkip"));
|
|
1318
|
+
} else {
|
|
1319
|
+
setError((res && res.message) || t("installFailed"));
|
|
1320
|
+
}
|
|
1321
|
+
} catch (e) {
|
|
1322
|
+
setError((e && e.message) || String(e));
|
|
1323
|
+
} finally {
|
|
1324
|
+
setSelfUpdateBusy(false);
|
|
1325
|
+
}
|
|
1326
|
+
})();
|
|
1327
|
+
};
|
|
1328
|
+
|
|
1329
|
+
const selfUpdateOriginLabel =
|
|
1330
|
+
selfUpdate && selfUpdate.origin === "gitee"
|
|
1331
|
+
? t("selfUpdateOriginGitee")
|
|
1332
|
+
: selfUpdate && selfUpdate.origin === "github"
|
|
1333
|
+
? t("selfUpdateOriginGithub")
|
|
1334
|
+
: t("selfUpdateOriginNpm");
|
|
1335
|
+
|
|
1241
1336
|
return h(
|
|
1242
1337
|
"div",
|
|
1243
1338
|
{ style: styles.page },
|
|
@@ -1250,6 +1345,36 @@ window.__ModuleLoader__.load({
|
|
|
1250
1345
|
h("h1", { style: styles.title }, t("panel")),
|
|
1251
1346
|
h("p", { style: styles.intro }, t("intro")),
|
|
1252
1347
|
),
|
|
1348
|
+
selfUpdate && selfUpdate.updateAvailable
|
|
1349
|
+
? h(
|
|
1350
|
+
"div",
|
|
1351
|
+
{ style: styles.updateBox },
|
|
1352
|
+
h(
|
|
1353
|
+
"div",
|
|
1354
|
+
{ style: styles.updateText },
|
|
1355
|
+
h("strong", null, t("selfUpdateTitle")),
|
|
1356
|
+
h(
|
|
1357
|
+
"span",
|
|
1358
|
+
null,
|
|
1359
|
+
format(t("selfUpdateDesc"), {
|
|
1360
|
+
current: selfUpdate.currentVersion || "?",
|
|
1361
|
+
latest: selfUpdate.latestVersion || "?",
|
|
1362
|
+
origin: selfUpdateOriginLabel,
|
|
1363
|
+
}),
|
|
1364
|
+
),
|
|
1365
|
+
selfUpdate.isFileInstall ? h("span", null, t("selfUpdateFileSkip")) : null,
|
|
1366
|
+
),
|
|
1367
|
+
h(
|
|
1368
|
+
Btn,
|
|
1369
|
+
{
|
|
1370
|
+
size: "sm",
|
|
1371
|
+
disabled: selfUpdateBusy || busy || !!selfUpdate.isFileInstall,
|
|
1372
|
+
onClick: onUpdateSelf,
|
|
1373
|
+
},
|
|
1374
|
+
selfUpdateBusy ? t("selfUpdateUpdating") : t("selfUpdateNow"),
|
|
1375
|
+
),
|
|
1376
|
+
)
|
|
1377
|
+
: null,
|
|
1253
1378
|
h(
|
|
1254
1379
|
"div",
|
|
1255
1380
|
{ style: styles.card },
|
|
@@ -1517,6 +1642,10 @@ window.__ModuleLoader__.load({
|
|
|
1517
1642
|
const SEARCH_RESULT = strictJson("dsh-plugin-discovery#pluginDiscovery/search:result");
|
|
1518
1643
|
const CATALOG_OPTIONS_CODEC = strictJson("dsh-plugin-discovery#pluginDiscovery/listCatalog:options");
|
|
1519
1644
|
const CATALOG_RESULT = strictJson("dsh-plugin-discovery#pluginDiscovery/listCatalog:result");
|
|
1645
|
+
const SELF_UPDATE_OPTIONS = strictJson("dsh-plugin-discovery#pluginDiscovery/checkSelfUpdate:options");
|
|
1646
|
+
const SELF_UPDATE_RESULT = strictJson("dsh-plugin-discovery#pluginDiscovery/checkSelfUpdate:result");
|
|
1647
|
+
const SELF_UPDATE_APPLY_OPTIONS = strictJson("dsh-plugin-discovery#pluginDiscovery/updateSelf:options");
|
|
1648
|
+
const SELF_UPDATE_APPLY_RESULT = strictJson("dsh-plugin-discovery#pluginDiscovery/updateSelf:result");
|
|
1520
1649
|
const INPUT_CODEC = strictJson("dsh-plugin-discovery#pluginDiscovery/resolve:input");
|
|
1521
1650
|
const RESOLVE_RESULT = strictJson("dsh-plugin-discovery#pluginDiscovery/resolve:result");
|
|
1522
1651
|
const SPEC_CODEC = strictJson("dsh-plugin-discovery#pluginDiscovery/installPlugin:spec");
|
|
@@ -1564,6 +1693,40 @@ window.__ModuleLoader__.load({
|
|
|
1564
1693
|
],
|
|
1565
1694
|
result: CATALOG_RESULT,
|
|
1566
1695
|
},
|
|
1696
|
+
{
|
|
1697
|
+
id: "dsh-plugin-discovery#pluginDiscovery/checkSelfUpdate",
|
|
1698
|
+
service: "pluginDiscovery",
|
|
1699
|
+
namespace: "pluginDiscovery",
|
|
1700
|
+
method: "checkSelfUpdate",
|
|
1701
|
+
invocation: { kind: "direct" },
|
|
1702
|
+
parameters: [
|
|
1703
|
+
{
|
|
1704
|
+
name: "options",
|
|
1705
|
+
wire: "options",
|
|
1706
|
+
source: "json",
|
|
1707
|
+
acceptsUndefined: true,
|
|
1708
|
+
codec: SELF_UPDATE_OPTIONS,
|
|
1709
|
+
},
|
|
1710
|
+
],
|
|
1711
|
+
result: SELF_UPDATE_RESULT,
|
|
1712
|
+
},
|
|
1713
|
+
{
|
|
1714
|
+
id: "dsh-plugin-discovery#pluginDiscovery/updateSelf",
|
|
1715
|
+
service: "pluginDiscovery",
|
|
1716
|
+
namespace: "pluginDiscovery",
|
|
1717
|
+
method: "updateSelf",
|
|
1718
|
+
invocation: { kind: "direct" },
|
|
1719
|
+
parameters: [
|
|
1720
|
+
{
|
|
1721
|
+
name: "options",
|
|
1722
|
+
wire: "options",
|
|
1723
|
+
source: "json",
|
|
1724
|
+
acceptsUndefined: true,
|
|
1725
|
+
codec: SELF_UPDATE_APPLY_OPTIONS,
|
|
1726
|
+
},
|
|
1727
|
+
],
|
|
1728
|
+
result: SELF_UPDATE_APPLY_RESULT,
|
|
1729
|
+
},
|
|
1567
1730
|
{
|
|
1568
1731
|
id: "dsh-plugin-discovery#pluginDiscovery/resolve",
|
|
1569
1732
|
service: "pluginDiscovery",
|
|
@@ -1657,6 +1820,35 @@ window.__ModuleLoader__.load({
|
|
|
1657
1820
|
}
|
|
1658
1821
|
return value;
|
|
1659
1822
|
},
|
|
1823
|
+
checkSelfUpdate: async (opts) => {
|
|
1824
|
+
if (mountError) return { ok: false, message: mountError };
|
|
1825
|
+
const remote = remoteNs();
|
|
1826
|
+
if (!remote || typeof remote.checkSelfUpdate !== "function") {
|
|
1827
|
+
return { ok: false, message: "remote.pluginDiscovery.checkSelfUpdate unavailable" };
|
|
1828
|
+
}
|
|
1829
|
+
const wire = await remote.checkSelfUpdate(opts || {});
|
|
1830
|
+
const value = unwrapRemote(wire, "checkSelfUpdate failed");
|
|
1831
|
+
if (wire && wire.ok === false) return { ok: false, message: value.message, update: value.update };
|
|
1832
|
+
return value;
|
|
1833
|
+
},
|
|
1834
|
+
updateSelf: async (opts) => {
|
|
1835
|
+
if (mountError) return { ok: false, message: mountError };
|
|
1836
|
+
const remote = remoteNs();
|
|
1837
|
+
if (!remote || typeof remote.updateSelf !== "function") {
|
|
1838
|
+
return { ok: false, message: "remote.pluginDiscovery.updateSelf unavailable" };
|
|
1839
|
+
}
|
|
1840
|
+
const wire = await remote.updateSelf(opts || {});
|
|
1841
|
+
const value = unwrapRemote(wire, "updateSelf failed");
|
|
1842
|
+
if (wire && wire.ok === false) {
|
|
1843
|
+
return {
|
|
1844
|
+
ok: false,
|
|
1845
|
+
message: value.message,
|
|
1846
|
+
skipped: !!(value && value.skipped),
|
|
1847
|
+
update: value.update,
|
|
1848
|
+
};
|
|
1849
|
+
}
|
|
1850
|
+
return value;
|
|
1851
|
+
},
|
|
1660
1852
|
resolve: async (input) => {
|
|
1661
1853
|
if (mountError) return { ok: false, message: mountError };
|
|
1662
1854
|
const remote = remoteNs();
|
package/cordis.patch.yml
CHANGED
|
@@ -13,3 +13,7 @@
|
|
|
13
13
|
catalogGiteeUrl: 'https://gitee.com/hjhcos/dsh-plugin-discovery/raw/main/data/recommended.json'
|
|
14
14
|
catalogGithubUrl: 'https://raw.githubusercontent.com/hjhcos/dsh-plugin-discovery/main/data/recommended.json'
|
|
15
15
|
catalogRefreshTtlMs: 3600000
|
|
16
|
+
# 本插件自动检查 npm 最新版(Gitee package.json 兜底);非 file: 安装时可自动更新
|
|
17
|
+
selfUpdateCheckEnabled: true
|
|
18
|
+
selfUpdateAuto: true
|
|
19
|
+
selfUpdateCheckTtlMs: 21600000
|
package/index.js
CHANGED
|
@@ -14,6 +14,90 @@ const DEFAULT_CATALOG_GITEE_URL =
|
|
|
14
14
|
"https://gitee.com/hjhcos/dsh-plugin-discovery/raw/main/data/recommended.json";
|
|
15
15
|
const DEFAULT_CATALOG_GITHUB_URL =
|
|
16
16
|
"https://raw.githubusercontent.com/hjhcos/dsh-plugin-discovery/main/data/recommended.json";
|
|
17
|
+
const SELF_PACKAGE_NAME = "dsh-plugin-discovery";
|
|
18
|
+
const DEFAULT_SELF_PACKAGE_GITEE_URL =
|
|
19
|
+
"https://gitee.com/hjhcos/dsh-plugin-discovery/raw/main/package.json";
|
|
20
|
+
const DEFAULT_SELF_PACKAGE_GITHUB_URL =
|
|
21
|
+
"https://raw.githubusercontent.com/hjhcos/dsh-plugin-discovery/main/package.json";
|
|
22
|
+
|
|
23
|
+
function readLocalPackageMeta() {
|
|
24
|
+
try {
|
|
25
|
+
const raw = readFileSync(join(__dirname, "package.json"), "utf8");
|
|
26
|
+
const data = JSON.parse(raw);
|
|
27
|
+
return {
|
|
28
|
+
name: data.name || SELF_PACKAGE_NAME,
|
|
29
|
+
version: String(data.version || "0.0.0"),
|
|
30
|
+
};
|
|
31
|
+
} catch {
|
|
32
|
+
return { name: SELF_PACKAGE_NAME, version: "0.0.0" };
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function parseSemverParts(version) {
|
|
37
|
+
const cleaned = String(version || "0")
|
|
38
|
+
.trim()
|
|
39
|
+
.replace(/^v/i, "")
|
|
40
|
+
.split("-")[0]
|
|
41
|
+
.split("+")[0];
|
|
42
|
+
return cleaned.split(".").map((part) => {
|
|
43
|
+
const n = Number.parseInt(part, 10);
|
|
44
|
+
return Number.isFinite(n) ? n : 0;
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** @returns {number} 1 if a>b, -1 if a<b, 0 if equal */
|
|
49
|
+
function compareSemver(a, b) {
|
|
50
|
+
const pa = parseSemverParts(a);
|
|
51
|
+
const pb = parseSemverParts(b);
|
|
52
|
+
const len = Math.max(pa.length, pb.length);
|
|
53
|
+
for (let i = 0; i < len; i += 1) {
|
|
54
|
+
const x = pa[i] || 0;
|
|
55
|
+
const y = pb[i] || 0;
|
|
56
|
+
if (x > y) return 1;
|
|
57
|
+
if (x < y) return -1;
|
|
58
|
+
}
|
|
59
|
+
return 0;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async function fetchJsonWithTimeout(url, timeoutMs = 12000) {
|
|
63
|
+
const trimmed = String(url || "").trim();
|
|
64
|
+
if (!trimmed) return null;
|
|
65
|
+
const controller = new AbortController();
|
|
66
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
67
|
+
try {
|
|
68
|
+
const response = await fetch(trimmed, {
|
|
69
|
+
headers: {
|
|
70
|
+
Accept: "application/json,text/plain,*/*",
|
|
71
|
+
"User-Agent": USER_AGENT,
|
|
72
|
+
},
|
|
73
|
+
signal: controller.signal,
|
|
74
|
+
});
|
|
75
|
+
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
|
76
|
+
return await response.json();
|
|
77
|
+
} finally {
|
|
78
|
+
clearTimeout(timer);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
async function fetchLatestSelfVersion(urls) {
|
|
83
|
+
const errors = [];
|
|
84
|
+
for (const entry of urls) {
|
|
85
|
+
try {
|
|
86
|
+
if (entry.kind === "npm") {
|
|
87
|
+
const data = await fetchJsonWithTimeout(entry.url);
|
|
88
|
+
const version = data?.version || data?.["dist-tags"]?.latest;
|
|
89
|
+
if (version) return { version: String(version), origin: "npm", url: entry.url };
|
|
90
|
+
} else {
|
|
91
|
+
const data = await fetchJsonWithTimeout(entry.url);
|
|
92
|
+
const version = data?.version;
|
|
93
|
+
if (version) return { version: String(version), origin: entry.label || "remote", url: entry.url };
|
|
94
|
+
}
|
|
95
|
+
} catch (error) {
|
|
96
|
+
errors.push(`${entry.label || entry.kind}: ${error instanceof Error ? error.message : String(error)}`);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return { version: null, origin: null, url: null, error: errors.join("; ") || "latest version unavailable" };
|
|
100
|
+
}
|
|
17
101
|
|
|
18
102
|
function emptyCatalog(error) {
|
|
19
103
|
return {
|
|
@@ -381,6 +465,14 @@ class PluginDiscoveryService extends TypertRemoteService {
|
|
|
381
465
|
catalogGithubUrl: z.string().default(DEFAULT_CATALOG_GITHUB_URL),
|
|
382
466
|
/** 远程检查间隔;期间复用内存/磁盘缓存 */
|
|
383
467
|
catalogRefreshTtlMs: z.number().min(0).max(86400000 * 7).default(3600000),
|
|
468
|
+
/** 启动与打开面板时检查本插件是否有新版本 */
|
|
469
|
+
selfUpdateCheckEnabled: z.boolean().default(true),
|
|
470
|
+
/** 发现新版本后自动执行更新(file: 本地开发安装会跳过自动更新) */
|
|
471
|
+
selfUpdateAuto: z.boolean().default(true),
|
|
472
|
+
selfUpdateCheckTtlMs: z.number().min(0).max(86400000 * 7).default(21600000),
|
|
473
|
+
selfPackageName: z.string().default(SELF_PACKAGE_NAME),
|
|
474
|
+
selfPackageGiteeUrl: z.string().default(DEFAULT_SELF_PACKAGE_GITEE_URL),
|
|
475
|
+
selfPackageGithubUrl: z.string().default(DEFAULT_SELF_PACKAGE_GITHUB_URL),
|
|
384
476
|
});
|
|
385
477
|
|
|
386
478
|
constructor(ctx, config) {
|
|
@@ -390,14 +482,206 @@ class PluginDiscoveryService extends TypertRemoteService {
|
|
|
390
482
|
this.catalog = pickNewerCatalog(loadBundledCatalog(), loadCachedCatalog());
|
|
391
483
|
this.catalogCheckedAt = 0;
|
|
392
484
|
this.catalogRefreshPromise = null;
|
|
485
|
+
this.selfUpdateCheckedAt = 0;
|
|
486
|
+
this.selfUpdateState = null;
|
|
487
|
+
this.selfUpdatePromise = null;
|
|
393
488
|
this.ctx.effect(() => this.registerTools());
|
|
394
489
|
// 启动后后台拉一次,不阻塞启动
|
|
395
490
|
this.ctx.effect(() => {
|
|
396
491
|
void this.refreshCatalog({ force: false }).catch(() => {});
|
|
492
|
+
void this.checkSelfUpdate({ force: false, auto: this.config.selfUpdateAuto === true }).catch(() => {});
|
|
397
493
|
return () => {};
|
|
398
494
|
});
|
|
399
495
|
}
|
|
400
496
|
|
|
497
|
+
getLocalInstallMeta() {
|
|
498
|
+
const local = readLocalPackageMeta();
|
|
499
|
+
const real = (() => {
|
|
500
|
+
try {
|
|
501
|
+
return join(__dirname);
|
|
502
|
+
} catch {
|
|
503
|
+
return "";
|
|
504
|
+
}
|
|
505
|
+
})();
|
|
506
|
+
const isFileInstall = /[\\/]bundles[\\/]dsh-plugin-discovery$/i.test(real.replace(/\\/g, "/"));
|
|
507
|
+
return {
|
|
508
|
+
name: this.config.selfPackageName || local.name || SELF_PACKAGE_NAME,
|
|
509
|
+
version: local.version,
|
|
510
|
+
isFileInstall,
|
|
511
|
+
};
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
selfUpdateSourceUrls() {
|
|
515
|
+
const name = this.config.selfPackageName || SELF_PACKAGE_NAME;
|
|
516
|
+
const urls = [
|
|
517
|
+
{
|
|
518
|
+
kind: "npm",
|
|
519
|
+
label: "npm",
|
|
520
|
+
url: `https://registry.npmjs.org/${encodeURIComponent(name)}/latest`,
|
|
521
|
+
},
|
|
522
|
+
];
|
|
523
|
+
const gitee = String(this.config.selfPackageGiteeUrl || "").trim();
|
|
524
|
+
const github = String(this.config.selfPackageGithubUrl || "").trim();
|
|
525
|
+
if (gitee) urls.push({ kind: "json", label: "gitee", url: gitee });
|
|
526
|
+
if (github) urls.push({ kind: "json", label: "github", url: github });
|
|
527
|
+
return urls;
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
async resolveInstalledSelfVersion() {
|
|
531
|
+
const local = this.getLocalInstallMeta();
|
|
532
|
+
try {
|
|
533
|
+
const bundles = await this.ctx.pluginManager.listBundles();
|
|
534
|
+
const row = (Array.isArray(bundles) ? bundles : []).find((item) => item && item.name === local.name);
|
|
535
|
+
if (row?.version) {
|
|
536
|
+
return { currentVersion: String(row.version), source: "bundle", isFileInstall: local.isFileInstall, name: local.name };
|
|
537
|
+
}
|
|
538
|
+
} catch {
|
|
539
|
+
/* ignore */
|
|
540
|
+
}
|
|
541
|
+
return {
|
|
542
|
+
currentVersion: local.version,
|
|
543
|
+
source: "package",
|
|
544
|
+
isFileInstall: local.isFileInstall,
|
|
545
|
+
name: local.name,
|
|
546
|
+
};
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
async checkSelfUpdate(options = {}) {
|
|
550
|
+
if (!this.config.enabled) {
|
|
551
|
+
return { ok: false, message: "发现插件已停用" };
|
|
552
|
+
}
|
|
553
|
+
if (this.config.selfUpdateCheckEnabled === false) {
|
|
554
|
+
return { ok: true, skipped: true, reason: "check disabled", update: this.selfUpdateState };
|
|
555
|
+
}
|
|
556
|
+
const force = options.force === true;
|
|
557
|
+
const auto = options.auto === true;
|
|
558
|
+
const ttl = this.config.selfUpdateCheckTtlMs ?? 21600000;
|
|
559
|
+
if (!force && this.selfUpdateCheckedAt && Date.now() - this.selfUpdateCheckedAt < ttl && this.selfUpdateState) {
|
|
560
|
+
if (auto && this.selfUpdateState.updateAvailable && !this.selfUpdateState.isFileInstall) {
|
|
561
|
+
return this.updateSelf({ latestVersion: this.selfUpdateState.latestVersion });
|
|
562
|
+
}
|
|
563
|
+
return { ok: true, skipped: true, reason: "ttl", update: this.selfUpdateState };
|
|
564
|
+
}
|
|
565
|
+
if (this.selfUpdatePromise) return this.selfUpdatePromise;
|
|
566
|
+
|
|
567
|
+
this.selfUpdatePromise = (async () => {
|
|
568
|
+
const installed = await this.resolveInstalledSelfVersion();
|
|
569
|
+
const latestInfo = await fetchLatestSelfVersion(this.selfUpdateSourceUrls());
|
|
570
|
+
this.selfUpdateCheckedAt = Date.now();
|
|
571
|
+
if (!latestInfo.version) {
|
|
572
|
+
this.selfUpdateState = {
|
|
573
|
+
...installed,
|
|
574
|
+
latestVersion: null,
|
|
575
|
+
updateAvailable: false,
|
|
576
|
+
origin: null,
|
|
577
|
+
checkedAt: this.selfUpdateCheckedAt,
|
|
578
|
+
error: latestInfo.error,
|
|
579
|
+
};
|
|
580
|
+
return { ok: false, message: latestInfo.error || "无法获取最新版本", update: this.selfUpdateState };
|
|
581
|
+
}
|
|
582
|
+
const updateAvailable = compareSemver(latestInfo.version, installed.currentVersion) > 0;
|
|
583
|
+
this.selfUpdateState = {
|
|
584
|
+
name: installed.name,
|
|
585
|
+
currentVersion: installed.currentVersion,
|
|
586
|
+
latestVersion: latestInfo.version,
|
|
587
|
+
updateAvailable,
|
|
588
|
+
origin: latestInfo.origin,
|
|
589
|
+
isFileInstall: installed.isFileInstall,
|
|
590
|
+
checkedAt: this.selfUpdateCheckedAt,
|
|
591
|
+
};
|
|
592
|
+
if (auto && updateAvailable && !installed.isFileInstall && this.config.selfUpdateAuto !== false) {
|
|
593
|
+
const updated = await this.updateSelf({ latestVersion: latestInfo.version });
|
|
594
|
+
return { ...updated, update: this.selfUpdateState };
|
|
595
|
+
}
|
|
596
|
+
return {
|
|
597
|
+
ok: true,
|
|
598
|
+
update: this.selfUpdateState,
|
|
599
|
+
message: updateAvailable
|
|
600
|
+
? `发现新版本 ${latestInfo.version}(当前 ${installed.currentVersion})`
|
|
601
|
+
: `已是最新版本 ${installed.currentVersion}`,
|
|
602
|
+
};
|
|
603
|
+
})().finally(() => {
|
|
604
|
+
this.selfUpdatePromise = null;
|
|
605
|
+
});
|
|
606
|
+
|
|
607
|
+
return this.selfUpdatePromise;
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
async updateSelf(options = {}) {
|
|
611
|
+
if (!this.config.enabled) {
|
|
612
|
+
return { ok: false, message: "发现插件已停用" };
|
|
613
|
+
}
|
|
614
|
+
const installed = await this.resolveInstalledSelfVersion();
|
|
615
|
+
if (installed.isFileInstall) {
|
|
616
|
+
return {
|
|
617
|
+
ok: false,
|
|
618
|
+
skipped: true,
|
|
619
|
+
message: "当前为本地 file: 开发安装,已跳过自动更新。发布版用户会从 npm 升级。",
|
|
620
|
+
update: this.selfUpdateState,
|
|
621
|
+
};
|
|
622
|
+
}
|
|
623
|
+
let latestVersion = typeof options.latestVersion === "string" ? options.latestVersion.trim() : "";
|
|
624
|
+
if (!latestVersion) {
|
|
625
|
+
const checked = await this.checkSelfUpdate({ force: true, auto: false });
|
|
626
|
+
latestVersion = checked?.update?.latestVersion || "";
|
|
627
|
+
if (!checked?.update?.updateAvailable) {
|
|
628
|
+
return {
|
|
629
|
+
ok: true,
|
|
630
|
+
updated: false,
|
|
631
|
+
message: checked?.message || "没有可更新版本",
|
|
632
|
+
update: checked?.update || this.selfUpdateState,
|
|
633
|
+
};
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
const name = installed.name || SELF_PACKAGE_NAME;
|
|
637
|
+
const spec = `${name}@${latestVersion}`;
|
|
638
|
+
const requestId =
|
|
639
|
+
globalThis.crypto?.randomUUID?.() || `pd-self-${Date.now()}-${Math.random().toString(16).slice(2)}`;
|
|
640
|
+
try {
|
|
641
|
+
const result = await this.ctx.pluginManager.installBundle(spec, {
|
|
642
|
+
requestId,
|
|
643
|
+
enabled: true,
|
|
644
|
+
});
|
|
645
|
+
const application = result?.application;
|
|
646
|
+
const failed = application === "failed" || application === "cancelled";
|
|
647
|
+
if (!failed) {
|
|
648
|
+
this.selfUpdateState = {
|
|
649
|
+
...(this.selfUpdateState || {}),
|
|
650
|
+
name,
|
|
651
|
+
currentVersion: latestVersion,
|
|
652
|
+
latestVersion,
|
|
653
|
+
updateAvailable: false,
|
|
654
|
+
origin: this.selfUpdateState?.origin || "npm",
|
|
655
|
+
isFileInstall: false,
|
|
656
|
+
checkedAt: Date.now(),
|
|
657
|
+
};
|
|
658
|
+
}
|
|
659
|
+
return {
|
|
660
|
+
ok: !failed,
|
|
661
|
+
updated: !failed,
|
|
662
|
+
spec,
|
|
663
|
+
requestId,
|
|
664
|
+
application,
|
|
665
|
+
packageName: name,
|
|
666
|
+
version: latestVersion,
|
|
667
|
+
message: failed
|
|
668
|
+
? result?.error?.message || `更新失败(${application || "unknown"})`
|
|
669
|
+
: `已更新到 ${latestVersion},请刷新页面或重启 dsh web 使界面生效`,
|
|
670
|
+
update: this.selfUpdateState,
|
|
671
|
+
result,
|
|
672
|
+
};
|
|
673
|
+
} catch (error) {
|
|
674
|
+
return {
|
|
675
|
+
ok: false,
|
|
676
|
+
updated: false,
|
|
677
|
+
spec,
|
|
678
|
+
requestId,
|
|
679
|
+
message: error instanceof Error ? error.message : String(error),
|
|
680
|
+
update: this.selfUpdateState,
|
|
681
|
+
};
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
|
|
401
685
|
getCatalog() {
|
|
402
686
|
return this.catalog || loadBundledCatalog();
|
|
403
687
|
}
|
|
@@ -573,6 +857,14 @@ class PluginDiscoveryService extends TypertRemoteService {
|
|
|
573
857
|
|
|
574
858
|
// 打开推荐/分类时尝试按 TTL 拉远程;失败静默回退本地
|
|
575
859
|
await this.refreshCatalog({ force: opts.refresh === true }).catch(() => {});
|
|
860
|
+
const selfUpdate = await this.checkSelfUpdate({
|
|
861
|
+
force: opts.refresh === true,
|
|
862
|
+
auto: this.config.selfUpdateAuto === true,
|
|
863
|
+
}).catch((error) => ({
|
|
864
|
+
ok: false,
|
|
865
|
+
message: error instanceof Error ? error.message : String(error),
|
|
866
|
+
update: this.selfUpdateState,
|
|
867
|
+
}));
|
|
576
868
|
|
|
577
869
|
const catalog = this.getCatalog();
|
|
578
870
|
const personalize = mode === "recommend";
|
|
@@ -594,6 +886,9 @@ class PluginDiscoveryService extends TypertRemoteService {
|
|
|
594
886
|
categories: catalog.categories,
|
|
595
887
|
results,
|
|
596
888
|
total: results.length,
|
|
889
|
+
selfUpdate: selfUpdate?.update || this.selfUpdateState,
|
|
890
|
+
selfUpdateMessage: selfUpdate?.message || null,
|
|
891
|
+
selfUpdated: selfUpdate?.updated === true,
|
|
597
892
|
};
|
|
598
893
|
}
|
|
599
894
|
|
|
@@ -929,6 +1224,28 @@ class PluginDiscoveryService extends TypertRemoteService {
|
|
|
929
1224
|
}),
|
|
930
1225
|
),
|
|
931
1226
|
);
|
|
1227
|
+
disposers.push(
|
|
1228
|
+
this.ctx.tools.register(
|
|
1229
|
+
defineTool({
|
|
1230
|
+
name: "plugin_discovery_check_update",
|
|
1231
|
+
description: "Check whether dsh-plugin-discovery has a newer npm/Gitee version and optionally update.",
|
|
1232
|
+
parameters: {
|
|
1233
|
+
autoUpdate: {
|
|
1234
|
+
type: "boolean",
|
|
1235
|
+
description: "If true and a newer version exists, install it now",
|
|
1236
|
+
},
|
|
1237
|
+
},
|
|
1238
|
+
output: toolOutput,
|
|
1239
|
+
async execute(args) {
|
|
1240
|
+
return service.checkSelfUpdate({
|
|
1241
|
+
force: true,
|
|
1242
|
+
auto: args.autoUpdate === true,
|
|
1243
|
+
});
|
|
1244
|
+
},
|
|
1245
|
+
presentCall: () => present("Check dsh-plugin-discovery update"),
|
|
1246
|
+
}),
|
|
1247
|
+
),
|
|
1248
|
+
);
|
|
932
1249
|
return () => {
|
|
933
1250
|
for (const dispose of disposers.reverse()) dispose();
|
|
934
1251
|
};
|
|
@@ -939,6 +1256,8 @@ class PluginDiscoveryService extends TypertRemoteService {
|
|
|
939
1256
|
|
|
940
1257
|
markRemoteMethod(PluginDiscoveryService.prototype, "search");
|
|
941
1258
|
markRemoteMethod(PluginDiscoveryService.prototype, "listCatalog");
|
|
1259
|
+
markRemoteMethod(PluginDiscoveryService.prototype, "checkSelfUpdate");
|
|
1260
|
+
markRemoteMethod(PluginDiscoveryService.prototype, "updateSelf");
|
|
942
1261
|
markRemoteMethod(PluginDiscoveryService.prototype, "resolve");
|
|
943
1262
|
markRemoteMethod(PluginDiscoveryService.prototype, "installPlugin");
|
|
944
1263
|
markRemoteMethod(PluginDiscoveryService.prototype, "listInstalled");
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-plugin-discovery",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"description": "Discover and install community DSH bundles from the sidebar: newbie essentials,
|
|
5
|
+
"description": "Discover and install community DSH bundles from the sidebar: newbie essentials, recommendations, categories, self-update, and npm/GitHub search.",
|
|
6
6
|
"author": "hjhcos",
|
|
7
7
|
"license": "MIT",
|
|
8
8
|
"keywords": [
|