@xrxs-ai/dsh-xrxs-plugins-store 0.2.4 → 0.2.6
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/lib/client.js +144 -55
- package/lib/host/installer.d.ts +3 -0
- package/lib/host/installer.js +13 -2
- package/lib/host/routes.js +8 -1
- package/lib/host/self-update.d.ts +7 -2
- package/lib/host/self-update.js +7 -2
- package/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -462,6 +462,13 @@ function formatDate(text) {
|
|
|
462
462
|
if (d === null) return text;
|
|
463
463
|
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`;
|
|
464
464
|
}
|
|
465
|
+
function formatShortDate(text) {
|
|
466
|
+
const d = parseTime(text);
|
|
467
|
+
if (d === null) return text;
|
|
468
|
+
const now = /* @__PURE__ */ new Date();
|
|
469
|
+
const yearPrefix = d.getFullYear() === now.getFullYear() ? "" : `${String(d.getFullYear() % 100).padStart(2, "0")}-`;
|
|
470
|
+
return `${yearPrefix}${pad(d.getMonth() + 1)}-${pad(d.getDate())}`;
|
|
471
|
+
}
|
|
465
472
|
function formatRelative(text) {
|
|
466
473
|
const parsed = parseTime(text);
|
|
467
474
|
if (parsed === null) return text;
|
|
@@ -1042,30 +1049,8 @@ var MARKET_CSS = `
|
|
|
1042
1049
|
font-size: 13px; line-height: 20px; color: var(--dsw-alias-label-primary, #1f2329);
|
|
1043
1050
|
text-align: right; word-break: break-all;
|
|
1044
1051
|
}
|
|
1045
|
-
.xps-mk-
|
|
1046
|
-
|
|
1047
|
-
width: 100%; box-sizing: border-box; padding: 4px 8px;
|
|
1048
|
-
border: none; border-radius: 8px; background: transparent; cursor: pointer;
|
|
1049
|
-
color: var(--dsw-alias-label-primary, #1f2329);
|
|
1050
|
-
font-family: var(--dsw-font-mono, ui-monospace, SFMono-Regular, Menlo, monospace);
|
|
1051
|
-
font-size: 12px; line-height: 18px; text-align: left;
|
|
1052
|
-
transition: background-color 0.15s ease;
|
|
1053
|
-
}
|
|
1054
|
-
.xps-mk-version:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(31, 35, 41, 0.06)); }
|
|
1055
|
-
.xps-mk-version:active { background: var(--dsw-alias-interactive-bg-active, rgba(38, 49, 72, 0.1)); }
|
|
1056
|
-
.xps-mk-version:focus-visible {
|
|
1057
|
-
outline: 2px solid var(--dsw-alias-brand-primary, #3370ff);
|
|
1058
|
-
outline-offset: -2px;
|
|
1059
|
-
}
|
|
1060
|
-
.xps-mk-version-active, .xps-mk-version-active:hover {
|
|
1061
|
-
background: var(--dsw-alias-interactive-bg-active, rgba(38, 49, 72, 0.1));
|
|
1062
|
-
color: var(--dsw-alias-label-primary, #1f2329);
|
|
1063
|
-
}
|
|
1064
|
-
.xps-mk-version-date {
|
|
1065
|
-
flex: none; font-size: 11px; line-height: 17px;
|
|
1066
|
-
font-family: inherit;
|
|
1067
|
-
color: var(--dsw-alias-label-tertiary, #8f959e);
|
|
1068
|
-
}
|
|
1052
|
+
/* \u8BE6\u60C5\u9875\u7248\u672C\u5386\u53F2\u4E0B\u62C9:\u5360\u6EE1\u5361\u7247\u5BBD(.xps-mk-select \u9ED8\u8BA4 flex:none \u53EA\u9002\u914D\u5DE5\u5177\u680F) */
|
|
1053
|
+
.xps-mk-version-select { display: block; width: 100%; margin-top: 8px; }
|
|
1069
1054
|
|
|
1070
1055
|
/* README markdown \u767D\u540D\u5355\u6E32\u67D3 */
|
|
1071
1056
|
.xps-md {
|
|
@@ -1125,8 +1110,7 @@ var MARKET_CSS = `
|
|
|
1125
1110
|
.xps-mk-chip,
|
|
1126
1111
|
.xps-mk-row,
|
|
1127
1112
|
.xps-mk-page,
|
|
1128
|
-
.xps-mk-select
|
|
1129
|
-
.xps-mk-version {
|
|
1113
|
+
.xps-mk-select {
|
|
1130
1114
|
transition: none;
|
|
1131
1115
|
}
|
|
1132
1116
|
.xps-mk-page:active:not(:disabled) {
|
|
@@ -2267,6 +2251,12 @@ function ConfirmDialog3(props) {
|
|
|
2267
2251
|
] })
|
|
2268
2252
|
] }) });
|
|
2269
2253
|
}
|
|
2254
|
+
function byInstalledAtDesc(a, b) {
|
|
2255
|
+
const ta = a.installedAt ?? "";
|
|
2256
|
+
const tb = b.installedAt ?? "";
|
|
2257
|
+
if (ta !== tb) return ta < tb ? 1 : -1;
|
|
2258
|
+
return a.name.localeCompare(b.name);
|
|
2259
|
+
}
|
|
2270
2260
|
function InstalledPage() {
|
|
2271
2261
|
ensureMarketStyles();
|
|
2272
2262
|
const { items, push, dismiss } = useToasts();
|
|
@@ -2280,7 +2270,7 @@ function InstalledPage() {
|
|
|
2280
2270
|
const load = (0, import_react5.useCallback)(() => {
|
|
2281
2271
|
setList(null);
|
|
2282
2272
|
setError(false);
|
|
2283
|
-
void getInstalled().then((installed) => setList(installed)).catch(() => {
|
|
2273
|
+
void getInstalled().then((installed) => setList([...installed].sort(byInstalledAtDesc))).catch(() => {
|
|
2284
2274
|
setList([]);
|
|
2285
2275
|
setError(true);
|
|
2286
2276
|
});
|
|
@@ -2549,6 +2539,24 @@ function satisfies(version, range) {
|
|
|
2549
2539
|
|
|
2550
2540
|
// src/client/pages/market/InstallButton.tsx
|
|
2551
2541
|
var import_react6 = require("react");
|
|
2542
|
+
|
|
2543
|
+
// src/client/pages/market/version.ts
|
|
2544
|
+
function compareVersions(a, b) {
|
|
2545
|
+
const parse = (text) => text.trim().replace(/^v/, "").split(".").map((part) => {
|
|
2546
|
+
const numeric = /^\d+/.exec(part);
|
|
2547
|
+
return numeric === null ? 0 : Number(numeric[0]);
|
|
2548
|
+
});
|
|
2549
|
+
const left = parse(a);
|
|
2550
|
+
const right = parse(b);
|
|
2551
|
+
const length = Math.max(left.length, right.length);
|
|
2552
|
+
for (let index = 0; index < length; index += 1) {
|
|
2553
|
+
const diff = (left[index] ?? 0) - (right[index] ?? 0);
|
|
2554
|
+
if (diff !== 0) return diff > 0 ? 1 : -1;
|
|
2555
|
+
}
|
|
2556
|
+
return 0;
|
|
2557
|
+
}
|
|
2558
|
+
|
|
2559
|
+
// src/client/pages/market/InstallButton.tsx
|
|
2552
2560
|
var import_jsx_runtime15 = require("react/jsx-runtime");
|
|
2553
2561
|
function InstallButton(props) {
|
|
2554
2562
|
const [phase, setPhase] = (0, import_react6.useState)("idle");
|
|
@@ -2556,10 +2564,11 @@ function InstallButton(props) {
|
|
|
2556
2564
|
const [buildConfirm, setBuildConfirm] = (0, import_react6.useState)(null);
|
|
2557
2565
|
const [approving, setApproving] = (0, import_react6.useState)(false);
|
|
2558
2566
|
const compat = checkCompat(props.compatRange, props.dshVersion);
|
|
2559
|
-
const
|
|
2560
|
-
const
|
|
2561
|
-
|
|
2562
|
-
|
|
2567
|
+
const sameAsInstalled = props.installed !== void 0 && props.installed.version === props.version;
|
|
2568
|
+
const verb = props.installed === void 0 || sameAsInstalled ? "\u5B89\u88C5" : compareVersions(props.version, props.installed.version) >= 0 ? "\u66F4\u65B0" : "\u964D\u7EA7";
|
|
2569
|
+
const variant = verb === "\u5B89\u88C5" ? "primary" : "update";
|
|
2570
|
+
if (sameAsInstalled) {
|
|
2571
|
+
return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "xps-mk-install", onClick: (e) => e.stopPropagation(), children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Button, { size: props.size ?? "medium", variant: "primary", disabled: true, title: "\u5DF2\u5B89\u88C5\u6B64\u7248\u672C,\u91CD\u88C5\u540C\u7248\u672C\u4E0D\u4F1A\u5237\u65B0", children: "\u5DF2\u5B89\u88C5" }) });
|
|
2563
2572
|
}
|
|
2564
2573
|
if (compat.ok === false) {
|
|
2565
2574
|
return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "xps-mk-install", onClick: (e) => e.stopPropagation(), children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
|
|
@@ -3233,12 +3242,32 @@ function PluginDetailPage(props) {
|
|
|
3233
3242
|
const [dshVersion, setDshVersion] = (0, import_react8.useState)(null);
|
|
3234
3243
|
const [installed, setInstalled] = (0, import_react8.useState)(null);
|
|
3235
3244
|
const toasts = useToasts();
|
|
3245
|
+
const installedSeq = (0, import_react8.useRef)(0);
|
|
3236
3246
|
const loadInstalled = (0, import_react8.useCallback)(
|
|
3237
|
-
(
|
|
3238
|
-
|
|
3247
|
+
(...names) => {
|
|
3248
|
+
const seq = ++installedSeq.current;
|
|
3249
|
+
getInstalled().then((list) => {
|
|
3250
|
+
if (seq === installedSeq.current) {
|
|
3251
|
+
setInstalled(list.find((item) => names.includes(item.name)) ?? null);
|
|
3252
|
+
}
|
|
3253
|
+
}).catch(() => {
|
|
3254
|
+
if (seq === installedSeq.current) setInstalled(null);
|
|
3255
|
+
});
|
|
3239
3256
|
},
|
|
3240
3257
|
[]
|
|
3241
3258
|
);
|
|
3259
|
+
const selectVersion = (version) => {
|
|
3260
|
+
setSelectedVersion(version);
|
|
3261
|
+
if (detail !== null) loadInstalled(pluginId, detail.name);
|
|
3262
|
+
};
|
|
3263
|
+
(0, import_react8.useEffect)(() => {
|
|
3264
|
+
if (detail === null) return;
|
|
3265
|
+
const onVisible = () => {
|
|
3266
|
+
if (document.visibilityState === "visible") loadInstalled(pluginId, detail.name);
|
|
3267
|
+
};
|
|
3268
|
+
document.addEventListener("visibilitychange", onVisible);
|
|
3269
|
+
return () => document.removeEventListener("visibilitychange", onVisible);
|
|
3270
|
+
}, [detail, pluginId, loadInstalled]);
|
|
3242
3271
|
(0, import_react8.useEffect)(() => {
|
|
3243
3272
|
let alive = true;
|
|
3244
3273
|
getHostInfo().then((info) => {
|
|
@@ -3255,7 +3284,6 @@ function PluginDetailPage(props) {
|
|
|
3255
3284
|
setDetail(null);
|
|
3256
3285
|
setSelectedVersion(null);
|
|
3257
3286
|
setInstalled(null);
|
|
3258
|
-
if (pluginId !== "") loadInstalled(pluginId);
|
|
3259
3287
|
getPluginDetail(pluginId).then((d) => {
|
|
3260
3288
|
if (!alive) return;
|
|
3261
3289
|
if (d === null) {
|
|
@@ -3264,6 +3292,7 @@ function PluginDetailPage(props) {
|
|
|
3264
3292
|
setDetail(d);
|
|
3265
3293
|
setSelectedVersion(d.version);
|
|
3266
3294
|
setStatus("ready");
|
|
3295
|
+
loadInstalled(pluginId, d.name);
|
|
3267
3296
|
}
|
|
3268
3297
|
}).catch(() => {
|
|
3269
3298
|
if (alive) setStatus("error");
|
|
@@ -3351,7 +3380,7 @@ function PluginDetailPage(props) {
|
|
|
3351
3380
|
sourceType: detail.sourceType,
|
|
3352
3381
|
dshVersion,
|
|
3353
3382
|
installed: installed ?? void 0,
|
|
3354
|
-
onInstalled: () => loadInstalled(detail.name),
|
|
3383
|
+
onInstalled: () => loadInstalled(pluginId, detail.name),
|
|
3355
3384
|
onToast: toasts.push
|
|
3356
3385
|
}
|
|
3357
3386
|
)
|
|
@@ -3380,24 +3409,22 @@ function PluginDetailPage(props) {
|
|
|
3380
3409
|
] }),
|
|
3381
3410
|
/* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className: "xps-mk-card", children: [
|
|
3382
3411
|
/* @__PURE__ */ (0, import_jsx_runtime19.jsx)("div", { className: "xps-mk-card-title", children: "\u7248\u672C\u5386\u53F2" }),
|
|
3383
|
-
|
|
3384
|
-
"
|
|
3412
|
+
/* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
|
|
3413
|
+
"select",
|
|
3385
3414
|
{
|
|
3386
|
-
|
|
3387
|
-
|
|
3388
|
-
title:
|
|
3389
|
-
|
|
3390
|
-
children: [
|
|
3391
|
-
|
|
3392
|
-
|
|
3393
|
-
|
|
3394
|
-
|
|
3395
|
-
|
|
3396
|
-
|
|
3397
|
-
|
|
3398
|
-
|
|
3399
|
-
v.version
|
|
3400
|
-
))
|
|
3415
|
+
className: "xps-mk-select xps-mk-version-select",
|
|
3416
|
+
value: displayVersion,
|
|
3417
|
+
title: "\u5207\u6362\u7248\u672C",
|
|
3418
|
+
onChange: (e) => selectVersion(e.target.value),
|
|
3419
|
+
children: detail.versions.map((v) => /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("option", { value: v.version, children: [
|
|
3420
|
+
"v",
|
|
3421
|
+
v.version,
|
|
3422
|
+
" \xB7 ",
|
|
3423
|
+
formatShortDate(v.publishedAt),
|
|
3424
|
+
v.version === detail.version ? " \xB7 \u6700\u65B0" : ""
|
|
3425
|
+
] }, v.version))
|
|
3426
|
+
}
|
|
3427
|
+
)
|
|
3401
3428
|
] })
|
|
3402
3429
|
] })
|
|
3403
3430
|
] })
|
|
@@ -3652,21 +3679,82 @@ var rightStyle2 = {
|
|
|
3652
3679
|
alignItems: "flex-end",
|
|
3653
3680
|
gap: 4
|
|
3654
3681
|
};
|
|
3682
|
+
var PENDING_UPDATE_KEY = "xrxs-plugins-store:self-update-pending";
|
|
3683
|
+
function readPendingUpdate() {
|
|
3684
|
+
try {
|
|
3685
|
+
const raw = localStorage.getItem(PENDING_UPDATE_KEY);
|
|
3686
|
+
if (raw === null) return null;
|
|
3687
|
+
const parsed = JSON.parse(raw);
|
|
3688
|
+
return typeof parsed.version === "string" ? parsed.version : null;
|
|
3689
|
+
} catch {
|
|
3690
|
+
return null;
|
|
3691
|
+
}
|
|
3692
|
+
}
|
|
3693
|
+
function writePendingUpdate(version) {
|
|
3694
|
+
try {
|
|
3695
|
+
localStorage.setItem(PENDING_UPDATE_KEY, JSON.stringify({ version }));
|
|
3696
|
+
} catch {
|
|
3697
|
+
}
|
|
3698
|
+
}
|
|
3699
|
+
function clearPendingUpdate() {
|
|
3700
|
+
try {
|
|
3701
|
+
localStorage.removeItem(PENDING_UPDATE_KEY);
|
|
3702
|
+
} catch {
|
|
3703
|
+
}
|
|
3704
|
+
}
|
|
3655
3705
|
function messageOf3(error, fallback) {
|
|
3656
3706
|
return error instanceof Error && error.message !== "" ? error.message : fallback;
|
|
3657
3707
|
}
|
|
3708
|
+
function versionAtLeast(current, target) {
|
|
3709
|
+
const parse = (text) => text.trim().replace(/^v/, "").split(".").map((part) => {
|
|
3710
|
+
const numeric = /^\d+/.exec(part);
|
|
3711
|
+
return numeric === null ? 0 : Number(numeric[0]);
|
|
3712
|
+
});
|
|
3713
|
+
const left = parse(current);
|
|
3714
|
+
const right = parse(target);
|
|
3715
|
+
const length = Math.max(left.length, right.length);
|
|
3716
|
+
for (let index = 0; index < length; index += 1) {
|
|
3717
|
+
const diff = (left[index] ?? 0) - (right[index] ?? 0);
|
|
3718
|
+
if (diff !== 0) return diff > 0;
|
|
3719
|
+
}
|
|
3720
|
+
return true;
|
|
3721
|
+
}
|
|
3658
3722
|
function GeneralUpdateRow() {
|
|
3659
|
-
const [phase, setPhase] = (0, import_react11.useState)(
|
|
3723
|
+
const [phase, setPhase] = (0, import_react11.useState)(() => {
|
|
3724
|
+
const pending = readPendingUpdate();
|
|
3725
|
+
return pending !== null ? { kind: "updated", version: pending } : { kind: "checking" };
|
|
3726
|
+
});
|
|
3660
3727
|
const [error, setError] = (0, import_react11.useState)("");
|
|
3661
3728
|
const check = () => {
|
|
3662
3729
|
setPhase({ kind: "checking" });
|
|
3663
3730
|
setError("");
|
|
3664
|
-
void checkSelfUpdate().then((result) =>
|
|
3731
|
+
void checkSelfUpdate().then((result) => {
|
|
3732
|
+
const pending = readPendingUpdate();
|
|
3733
|
+
if (pending !== null && result.current !== null && versionAtLeast(result.current, pending)) {
|
|
3734
|
+
clearPendingUpdate();
|
|
3735
|
+
setPhase({ kind: "checked", check: result });
|
|
3736
|
+
return;
|
|
3737
|
+
}
|
|
3738
|
+
setPhase({ kind: "checked", check: result });
|
|
3739
|
+
}).catch((err) => {
|
|
3665
3740
|
setPhase({ kind: "checked", check: { current: null, latest: null, updateAvailable: false } });
|
|
3666
3741
|
setError(messageOf3(err, "\u68C0\u67E5\u66F4\u65B0\u5931\u8D25"));
|
|
3667
3742
|
});
|
|
3668
3743
|
};
|
|
3669
|
-
(0, import_react11.useEffect)(
|
|
3744
|
+
(0, import_react11.useEffect)(() => {
|
|
3745
|
+
if (readPendingUpdate() !== null) {
|
|
3746
|
+
void checkSelfUpdate().then((result) => {
|
|
3747
|
+
const pending = readPendingUpdate();
|
|
3748
|
+
if (pending !== null && result.current !== null && versionAtLeast(result.current, pending)) {
|
|
3749
|
+
clearPendingUpdate();
|
|
3750
|
+
setPhase({ kind: "checked", check: result });
|
|
3751
|
+
}
|
|
3752
|
+
}).catch(() => {
|
|
3753
|
+
});
|
|
3754
|
+
return;
|
|
3755
|
+
}
|
|
3756
|
+
check();
|
|
3757
|
+
}, []);
|
|
3670
3758
|
const handleUpdate = (target, check0) => {
|
|
3671
3759
|
setPhase({ kind: "updating", check: check0 });
|
|
3672
3760
|
setError("");
|
|
@@ -3675,6 +3763,7 @@ function GeneralUpdateRow() {
|
|
|
3675
3763
|
setPhase({ kind: "degraded", command: result.command, version: target });
|
|
3676
3764
|
return;
|
|
3677
3765
|
}
|
|
3766
|
+
writePendingUpdate(target);
|
|
3678
3767
|
setPhase({ kind: "updated", version: target });
|
|
3679
3768
|
}).catch((err) => {
|
|
3680
3769
|
setPhase({ kind: "checked", check: check0 });
|
package/lib/host/installer.d.ts
CHANGED
|
@@ -88,6 +88,9 @@ export interface InstalledEntry {
|
|
|
88
88
|
managed: boolean;
|
|
89
89
|
/** 已装包自身 package.json 的 description(file: 本地装的包不在市场里,简介只能从这里来);读不到为 null。 */
|
|
90
90
|
description: string | null;
|
|
91
|
+
/** 安装时间(ISO 8601);取 node_modules/<name> 的创建时间(pnpm 装包时建立,更新重装会刷新)。
|
|
92
|
+
* 目录缺失/文件系统不支持 birthtime 时为 null,页面把它排在最后。 */
|
|
93
|
+
installedAt: string | null;
|
|
91
94
|
}
|
|
92
95
|
/** 已装列表:读 <DSH_HOME>/profiles/<profile>/package.json 依赖 + dsh.profile.bundles 层栈
|
|
93
96
|
* (desktop profile 被 CLI 独占时读文件不受影响;设计文档 §3.3)。
|
package/lib/host/installer.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto';
|
|
2
2
|
import { spawn } from 'node:child_process';
|
|
3
3
|
import { existsSync, readdirSync, statSync } from 'node:fs';
|
|
4
|
-
import { mkdir, readFile, rename, writeFile } from 'node:fs/promises';
|
|
4
|
+
import { lstat, mkdir, readFile, rename, writeFile } from 'node:fs/promises';
|
|
5
5
|
import { homedir } from 'node:os';
|
|
6
6
|
import { basename, delimiter, dirname, join } from 'node:path';
|
|
7
7
|
import { ApiError } from "./api-client.js";
|
|
@@ -437,7 +437,18 @@ export async function listInstalledFromProfile(host) {
|
|
|
437
437
|
}
|
|
438
438
|
if (version === '')
|
|
439
439
|
version = versionFromFileSpec(spec) ?? '';
|
|
440
|
-
|
|
440
|
+
// 安装时间:lstat 链接本身(pnpm 的 node_modules 项是指向 .pnpm 的符号链接,
|
|
441
|
+
// 链接创建时间 = 装包时间);birthtime 不可用(≤0/非法)退 mtime,再不行置 null
|
|
442
|
+
let installedAt = null;
|
|
443
|
+
try {
|
|
444
|
+
const link = await lstat(join(profileDir, 'node_modules', ...name.split('/')));
|
|
445
|
+
const time = link.birthtimeMs > 0 ? link.birthtime : link.mtime;
|
|
446
|
+
installedAt = Number.isNaN(time.getTime()) || time.getTime() <= 0 ? null : time.toISOString();
|
|
447
|
+
}
|
|
448
|
+
catch {
|
|
449
|
+
// 路径不存在:installedAt 保持 null
|
|
450
|
+
}
|
|
451
|
+
return { name, version, managed: bundles.has(name), description, installedAt };
|
|
441
452
|
}));
|
|
442
453
|
}
|
|
443
454
|
const OUTPUT_CAP = 512 * 1024;
|
package/lib/host/routes.js
CHANGED
|
@@ -301,7 +301,11 @@ async function fetchLatestVersionMap(deps) {
|
|
|
301
301
|
const version = optionalString(item.version);
|
|
302
302
|
if (name === undefined || version === undefined)
|
|
303
303
|
continue;
|
|
304
|
-
map.set(name, {
|
|
304
|
+
map.set(name, {
|
|
305
|
+
version,
|
|
306
|
+
displayName: optionalString(item.displayName) ?? null,
|
|
307
|
+
summary: optionalString(item.summary) ?? null,
|
|
308
|
+
});
|
|
305
309
|
}
|
|
306
310
|
}
|
|
307
311
|
catch (error) {
|
|
@@ -321,6 +325,9 @@ async function handleInstalled(rc) {
|
|
|
321
325
|
latestVersion,
|
|
322
326
|
updateAvailable: latestVersion !== null && latestVersion !== entry.version,
|
|
323
327
|
managed: entry.managed,
|
|
328
|
+
installedAt: entry.installedAt,
|
|
329
|
+
// 展示名来自市场;本机手装/离线的包没有,client 半自行回退包名
|
|
330
|
+
displayName: meta?.displayName ?? null,
|
|
324
331
|
// 市场简介优先;file: 本地装的包不在市场里,回退该包自己 package.json 的 description
|
|
325
332
|
summary: meta?.summary ?? entry.description,
|
|
326
333
|
};
|
|
@@ -12,7 +12,10 @@ import { type InstallOutcome, type InstallerDeps } from './installer.ts';
|
|
|
12
12
|
export declare const SELF_PACKAGE_NAME = "@xrxs-ai/dsh-xrxs-plugins-store";
|
|
13
13
|
/** 当前运行实例的版本(包根 package.json);读不到为 null(极端情况:裸文件运行)。
|
|
14
14
|
* 依次尝试:模块相对层级(tsc 产物 lib/host/ 与源码 src/host/ 都是上一级的上一级)、
|
|
15
|
-
* 按包名 resolve(profile 安装布局变化时的兜底);读到的包名必须匹配,防止层级猜错读成别的包。
|
|
15
|
+
* 按包名 resolve(profile 安装布局变化时的兜底);读到的包名必须匹配,防止层级猜错读成别的包。
|
|
16
|
+
* 模块级缓存且首次读取后不再重读:这里要报的是「运行实例」的版本,不是磁盘版本 ——
|
|
17
|
+
* 更新落盘后宿主可能只重挂 client 而不重载 host 模块,重读磁盘会把还在运行的旧实例
|
|
18
|
+
* 报成新版本;重启后 host 模块重新加载,缓存自然取到新版本。 */
|
|
16
19
|
export declare function currentSelfVersion(): string | null;
|
|
17
20
|
export interface SelfUpdateCheck {
|
|
18
21
|
/** 当前运行实例版本;读不到为 null(无法比较,不可更新) */
|
|
@@ -21,7 +24,9 @@ export interface SelfUpdateCheck {
|
|
|
21
24
|
latest: string | null;
|
|
22
25
|
updateAvailable: boolean;
|
|
23
26
|
}
|
|
24
|
-
/** 查 npm latest 并与当前版本比较;registry 查询失败时 latest 为 null(不抛,便于 UI 展示「检查失败」)。
|
|
27
|
+
/** 查 npm latest 并与当前版本比较;registry 查询失败时 latest 为 null(不抛,便于 UI 展示「检查失败」)。
|
|
28
|
+
* 当前版本走模块级缓存:报运行实例的版本 —— 更新落盘、宿主尚未重载时,磁盘已是新版本,
|
|
29
|
+
* 但内存里跑的还是旧实例,报旧值才能让 client 半的「已更新待重启」记录正确保留到重启。 */
|
|
25
30
|
export declare function checkSelfUpdate(): Promise<SelfUpdateCheck>;
|
|
26
31
|
/** 应用更新:复用安装链路装确切版本(不装 latest tag,避免「检查到装的」漂移)。 */
|
|
27
32
|
export declare function applySelfUpdate(version: string, deps: InstallerDeps): Promise<InstallOutcome>;
|
package/lib/host/self-update.js
CHANGED
|
@@ -20,7 +20,10 @@ const LATEST_TIMEOUT_MS = 10_000;
|
|
|
20
20
|
let cachedVersion = null;
|
|
21
21
|
/** 当前运行实例的版本(包根 package.json);读不到为 null(极端情况:裸文件运行)。
|
|
22
22
|
* 依次尝试:模块相对层级(tsc 产物 lib/host/ 与源码 src/host/ 都是上一级的上一级)、
|
|
23
|
-
* 按包名 resolve(profile 安装布局变化时的兜底);读到的包名必须匹配,防止层级猜错读成别的包。
|
|
23
|
+
* 按包名 resolve(profile 安装布局变化时的兜底);读到的包名必须匹配,防止层级猜错读成别的包。
|
|
24
|
+
* 模块级缓存且首次读取后不再重读:这里要报的是「运行实例」的版本,不是磁盘版本 ——
|
|
25
|
+
* 更新落盘后宿主可能只重挂 client 而不重载 host 模块,重读磁盘会把还在运行的旧实例
|
|
26
|
+
* 报成新版本;重启后 host 模块重新加载,缓存自然取到新版本。 */
|
|
24
27
|
export function currentSelfVersion() {
|
|
25
28
|
if (cachedVersion !== null)
|
|
26
29
|
return cachedVersion;
|
|
@@ -50,7 +53,9 @@ export function currentSelfVersion() {
|
|
|
50
53
|
}
|
|
51
54
|
return cachedVersion;
|
|
52
55
|
}
|
|
53
|
-
/** 查 npm latest 并与当前版本比较;registry 查询失败时 latest 为 null(不抛,便于 UI 展示「检查失败」)。
|
|
56
|
+
/** 查 npm latest 并与当前版本比较;registry 查询失败时 latest 为 null(不抛,便于 UI 展示「检查失败」)。
|
|
57
|
+
* 当前版本走模块级缓存:报运行实例的版本 —— 更新落盘、宿主尚未重载时,磁盘已是新版本,
|
|
58
|
+
* 但内存里跑的还是旧实例,报旧值才能让 client 半的「已更新待重启」记录正确保留到重启。 */
|
|
54
59
|
export async function checkSelfUpdate() {
|
|
55
60
|
const current = currentSelfVersion();
|
|
56
61
|
let latest = null;
|