@xrxs-ai/dsh-xrxs-plugins-store 0.2.2 → 0.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/lib/client.js +148 -9
- package/lib/host/routes.js +15 -0
- package/lib/host/self-update.d.ts +30 -0
- package/lib/host/self-update.js +97 -0
- package/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -30,7 +30,7 @@ __export(index_exports, {
|
|
|
30
30
|
inject: () => inject
|
|
31
31
|
});
|
|
32
32
|
module.exports = __toCommonJS(index_exports);
|
|
33
|
-
var
|
|
33
|
+
var import_react12 = require("react");
|
|
34
34
|
|
|
35
35
|
// src/client/MarketplaceApp.tsx
|
|
36
36
|
var import_react9 = require("react");
|
|
@@ -140,6 +140,12 @@ function installPlugin(name, version, sourceType) {
|
|
|
140
140
|
function uninstallPlugin(name) {
|
|
141
141
|
return hostPost("/api/uninstall", { name });
|
|
142
142
|
}
|
|
143
|
+
function checkSelfUpdate() {
|
|
144
|
+
return hostGet("/api/self-update");
|
|
145
|
+
}
|
|
146
|
+
function applySelfUpdate(version) {
|
|
147
|
+
return hostPost("/api/self-update/apply", { version });
|
|
148
|
+
}
|
|
143
149
|
|
|
144
150
|
// src/client/data/developer-api.ts
|
|
145
151
|
var BASE = "/api/developer/plugins";
|
|
@@ -3576,6 +3582,126 @@ function GeneralTokenRow() {
|
|
|
3576
3582
|
] });
|
|
3577
3583
|
}
|
|
3578
3584
|
|
|
3585
|
+
// src/client/settings/GeneralUpdateRow.tsx
|
|
3586
|
+
var import_react11 = require("react");
|
|
3587
|
+
var import_jsx_runtime22 = require("react/jsx-runtime");
|
|
3588
|
+
var rowStyle2 = {
|
|
3589
|
+
display: "flex",
|
|
3590
|
+
justifyContent: "space-between",
|
|
3591
|
+
alignItems: "center",
|
|
3592
|
+
gap: 24,
|
|
3593
|
+
padding: "16px 0",
|
|
3594
|
+
borderBottom: "0.5px solid var(--dsw-alias-border-l2, #dee0e3)"
|
|
3595
|
+
};
|
|
3596
|
+
var leftStyle2 = { minWidth: 0 };
|
|
3597
|
+
var titleStyle2 = {
|
|
3598
|
+
fontSize: 14,
|
|
3599
|
+
lineHeight: "20px",
|
|
3600
|
+
color: "var(--dsw-alias-label-primary, #1f2329)"
|
|
3601
|
+
};
|
|
3602
|
+
var descStyle2 = {
|
|
3603
|
+
marginTop: 4,
|
|
3604
|
+
fontSize: 12,
|
|
3605
|
+
lineHeight: "18px",
|
|
3606
|
+
color: "var(--dsw-alias-label-secondary, #646a73)"
|
|
3607
|
+
};
|
|
3608
|
+
var valueStyle2 = {
|
|
3609
|
+
fontSize: 13,
|
|
3610
|
+
lineHeight: "20px",
|
|
3611
|
+
color: "var(--dsw-alias-label-primary, #1f2329)"
|
|
3612
|
+
};
|
|
3613
|
+
var hintStyle2 = {
|
|
3614
|
+
fontSize: 12,
|
|
3615
|
+
lineHeight: "18px",
|
|
3616
|
+
color: "var(--dsw-alias-label-tertiary, #8f959e)"
|
|
3617
|
+
};
|
|
3618
|
+
var errorStyle2 = {
|
|
3619
|
+
...hintStyle2,
|
|
3620
|
+
color: "var(--dsw-alias-state-error-primary, #d83931)",
|
|
3621
|
+
whiteSpace: "pre-line"
|
|
3622
|
+
};
|
|
3623
|
+
var rightStyle2 = {
|
|
3624
|
+
flexShrink: 0,
|
|
3625
|
+
display: "flex",
|
|
3626
|
+
flexDirection: "column",
|
|
3627
|
+
alignItems: "flex-end",
|
|
3628
|
+
gap: 4
|
|
3629
|
+
};
|
|
3630
|
+
function messageOf3(error, fallback) {
|
|
3631
|
+
return error instanceof Error && error.message !== "" ? error.message : fallback;
|
|
3632
|
+
}
|
|
3633
|
+
function GeneralUpdateRow() {
|
|
3634
|
+
const [phase, setPhase] = (0, import_react11.useState)({ kind: "checking" });
|
|
3635
|
+
const [error, setError] = (0, import_react11.useState)("");
|
|
3636
|
+
const check = () => {
|
|
3637
|
+
setPhase({ kind: "checking" });
|
|
3638
|
+
setError("");
|
|
3639
|
+
void checkSelfUpdate().then((result) => setPhase({ kind: "checked", check: result })).catch((err) => {
|
|
3640
|
+
setPhase({ kind: "checked", check: { current: null, latest: null, updateAvailable: false } });
|
|
3641
|
+
setError(messageOf3(err, "\u68C0\u67E5\u66F4\u65B0\u5931\u8D25"));
|
|
3642
|
+
});
|
|
3643
|
+
};
|
|
3644
|
+
(0, import_react11.useEffect)(check, []);
|
|
3645
|
+
const handleUpdate = (target, check0) => {
|
|
3646
|
+
setPhase({ kind: "updating", check: check0 });
|
|
3647
|
+
setError("");
|
|
3648
|
+
void applySelfUpdate(target).then((result) => {
|
|
3649
|
+
if (result.command !== void 0 && result.command !== "") {
|
|
3650
|
+
setPhase({ kind: "degraded", command: result.command, version: target });
|
|
3651
|
+
return;
|
|
3652
|
+
}
|
|
3653
|
+
setPhase({ kind: "updated", version: target });
|
|
3654
|
+
}).catch((err) => {
|
|
3655
|
+
setPhase({ kind: "checked", check: check0 });
|
|
3656
|
+
setError(messageOf3(err, "\u66F4\u65B0\u5931\u8D25"));
|
|
3657
|
+
});
|
|
3658
|
+
};
|
|
3659
|
+
const checking = phase.kind === "checking";
|
|
3660
|
+
const updating = phase.kind === "updating";
|
|
3661
|
+
return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { style: rowStyle2, children: [
|
|
3662
|
+
/* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { style: leftStyle2, children: [
|
|
3663
|
+
/* @__PURE__ */ (0, import_jsx_runtime22.jsx)("div", { style: titleStyle2, children: "\u85AA\u4EBA\u85AA\u4E8B\u5E02\u573A \xB7 \u63D2\u4EF6\u66F4\u65B0" }),
|
|
3664
|
+
/* @__PURE__ */ (0, import_jsx_runtime22.jsx)("div", { style: descStyle2, children: phase.kind === "checked" && phase.check.current !== null ? `\u5F53\u524D\u7248\u672C v${phase.check.current}` : "\u81EA\u52A8\u68C0\u6D4B npm \u4E0A\u7684\u65B0\u7248\u672C,\u4E00\u952E\u66F4\u65B0(\u66F4\u65B0\u540E\u9700\u91CD\u542F\u5E94\u7528\u751F\u6548)" })
|
|
3665
|
+
] }),
|
|
3666
|
+
/* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { style: rightStyle2, children: [
|
|
3667
|
+
phase.kind === "checking" && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("div", { style: hintStyle2, children: "\u6B63\u5728\u68C0\u67E5\u66F4\u65B0\u2026" }),
|
|
3668
|
+
phase.kind === "checked" && (phase.check.updateAvailable && phase.check.latest !== null ? /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: 8 }, children: [
|
|
3669
|
+
/* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { style: valueStyle2, children: [
|
|
3670
|
+
"\u53D1\u73B0\u65B0\u7248\u672C v",
|
|
3671
|
+
phase.check.latest
|
|
3672
|
+
] }),
|
|
3673
|
+
/* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
|
|
3674
|
+
Button,
|
|
3675
|
+
{
|
|
3676
|
+
variant: "primary",
|
|
3677
|
+
size: "small",
|
|
3678
|
+
onClick: () => handleUpdate(phase.check.latest ?? "", phase.check),
|
|
3679
|
+
title: `\u66F4\u65B0\u5230 v${phase.check.latest}`,
|
|
3680
|
+
children: "\u4E00\u952E\u66F4\u65B0"
|
|
3681
|
+
}
|
|
3682
|
+
)
|
|
3683
|
+
] }) : /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: 8 }, children: [
|
|
3684
|
+
/* @__PURE__ */ (0, import_jsx_runtime22.jsx)("div", { style: hintStyle2, children: phase.check.latest === null ? "\u65E0\u6CD5\u83B7\u53D6\u6700\u65B0\u7248\u672C\u4FE1\u606F" : "\u5DF2\u662F\u6700\u65B0\u7248\u672C" }),
|
|
3685
|
+
/* @__PURE__ */ (0, import_jsx_runtime22.jsx)(Button, { size: "small", variant: "ghost", onClick: check, title: "\u91CD\u65B0\u68C0\u67E5 npm \u4E0A\u7684\u6700\u65B0\u7248\u672C", children: "\u91CD\u65B0\u68C0\u67E5" })
|
|
3686
|
+
] })),
|
|
3687
|
+
updating && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("div", { style: hintStyle2, children: "\u6B63\u5728\u66F4\u65B0,\u8BF7\u7A0D\u5019\u2026" }),
|
|
3688
|
+
phase.kind === "updated" && /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { style: valueStyle2, children: [
|
|
3689
|
+
"\u5DF2\u66F4\u65B0\u5230 v",
|
|
3690
|
+
phase.version,
|
|
3691
|
+
",\u5B8C\u5168\u9000\u51FA\u5E76\u91CD\u542F\u5E94\u7528\u540E\u751F\u6548"
|
|
3692
|
+
] }),
|
|
3693
|
+
phase.kind === "degraded" && /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { style: { maxWidth: 420, textAlign: "right" }, children: [
|
|
3694
|
+
/* @__PURE__ */ (0, import_jsx_runtime22.jsx)("div", { style: errorStyle2, children: "\u5BBF\u4E3B\u547D\u4EE4\u884C\u4E0D\u53EF\u7528,\u8BF7\u624B\u52A8\u6267\u884C\u4EE5\u4E0B\u547D\u4EE4\u540E\u91CD\u542F\u5E94\u7528:" }),
|
|
3695
|
+
/* @__PURE__ */ (0, import_jsx_runtime22.jsx)("div", { style: { ...hintStyle2, whiteSpace: "pre-wrap", wordBreak: "break-all" }, children: phase.command })
|
|
3696
|
+
] }),
|
|
3697
|
+
error !== "" && /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: 8 }, children: [
|
|
3698
|
+
/* @__PURE__ */ (0, import_jsx_runtime22.jsx)("div", { style: errorStyle2, children: error }),
|
|
3699
|
+
/* @__PURE__ */ (0, import_jsx_runtime22.jsx)(Button, { size: "small", variant: "ghost", onClick: check, disabled: checking, children: "\u91CD\u8BD5" })
|
|
3700
|
+
] })
|
|
3701
|
+
] })
|
|
3702
|
+
] });
|
|
3703
|
+
}
|
|
3704
|
+
|
|
3579
3705
|
// src/client/index.ts
|
|
3580
3706
|
var inject = ["slots"];
|
|
3581
3707
|
function injectTheme() {
|
|
@@ -3588,7 +3714,7 @@ function injectTheme() {
|
|
|
3588
3714
|
function StoreIcon(props) {
|
|
3589
3715
|
const active = props.active === true;
|
|
3590
3716
|
const size = typeof props.size === "number" ? props.size : 20;
|
|
3591
|
-
return (0,
|
|
3717
|
+
return (0, import_react12.createElement)(
|
|
3592
3718
|
"svg",
|
|
3593
3719
|
{
|
|
3594
3720
|
width: size,
|
|
@@ -3599,13 +3725,13 @@ function StoreIcon(props) {
|
|
|
3599
3725
|
color: active ? "var(--dsw-alias-brand-primary, #3861fb)" : "var(--dsw-alias-label-secondary, #646a73)"
|
|
3600
3726
|
}
|
|
3601
3727
|
},
|
|
3602
|
-
(0,
|
|
3728
|
+
(0, import_react12.createElement)("path", {
|
|
3603
3729
|
d: "M4 7h16l-1.2-3.2A1 1 0 0 0 17.85 3H6.15a1 1 0 0 0-.95.8L4 7Zm0 0v12a1 1 0 0 0 1 1h14a1 1 0 0 0 1-1V7",
|
|
3604
3730
|
stroke: "currentColor",
|
|
3605
3731
|
strokeWidth: 1.6,
|
|
3606
3732
|
strokeLinejoin: "round"
|
|
3607
3733
|
}),
|
|
3608
|
-
(0,
|
|
3734
|
+
(0, import_react12.createElement)("path", {
|
|
3609
3735
|
d: "M9 11a3 3 0 0 0 6 0",
|
|
3610
3736
|
stroke: "currentColor",
|
|
3611
3737
|
strokeWidth: 1.6,
|
|
@@ -3615,13 +3741,13 @@ function StoreIcon(props) {
|
|
|
3615
3741
|
}
|
|
3616
3742
|
function BundleConfigSeat(props) {
|
|
3617
3743
|
if (props.view === "page") {
|
|
3618
|
-
return (0,
|
|
3744
|
+
return (0, import_react12.createElement)(
|
|
3619
3745
|
"div",
|
|
3620
3746
|
{ className: "xps-page-placeholder" },
|
|
3621
3747
|
"\u85AA\u4EBA\u85AA\u4E8B\u5E02\u573A\u914D\u7F6E(\u5360\u4F4D):serviceBaseUrl / cacheDir / requestTimeout / cachePolicy"
|
|
3622
3748
|
);
|
|
3623
3749
|
}
|
|
3624
|
-
return (0,
|
|
3750
|
+
return (0, import_react12.createElement)("div", null, "\u85AA\u4EBA\u85AA\u4E8B\u5E02\u573A:\u79C1\u6709\u63D2\u4EF6\u6D4F\u89C8\u3001\u5B89\u88C5\u4E0E\u5F00\u53D1\u8005\u4E0A\u4F20");
|
|
3625
3751
|
}
|
|
3626
3752
|
function apply(ctx) {
|
|
3627
3753
|
const slots = ctx.get("slots");
|
|
@@ -3654,7 +3780,7 @@ function apply(ctx) {
|
|
|
3654
3780
|
"main",
|
|
3655
3781
|
() => safe(
|
|
3656
3782
|
"main",
|
|
3657
|
-
() => slots.register({ name: "main", key: "xrxs-plugins-store" }, () => (0,
|
|
3783
|
+
() => slots.register({ name: "main", key: "xrxs-plugins-store" }, () => (0, import_react12.createElement)(MarketplaceApp))
|
|
3658
3784
|
)
|
|
3659
3785
|
),
|
|
3660
3786
|
"xrxs-plugins-store.main"
|
|
@@ -3666,12 +3792,25 @@ function apply(ctx) {
|
|
|
3666
3792
|
"settings.general.item",
|
|
3667
3793
|
() => slots.register(
|
|
3668
3794
|
{ name: "settings.general.item", id: "xrxs-plugins-store-token", order: 50 },
|
|
3669
|
-
() => (0,
|
|
3795
|
+
() => (0, import_react12.createElement)(GeneralTokenRow)
|
|
3670
3796
|
)
|
|
3671
3797
|
)
|
|
3672
3798
|
),
|
|
3673
3799
|
"xrxs-plugins-store.settings.general-item"
|
|
3674
3800
|
);
|
|
3801
|
+
ctx.effect(
|
|
3802
|
+
() => slots.inject(
|
|
3803
|
+
"settings.general.item",
|
|
3804
|
+
() => safe(
|
|
3805
|
+
"settings.general.item",
|
|
3806
|
+
() => slots.register(
|
|
3807
|
+
{ name: "settings.general.item", id: "xrxs-plugins-store-update", order: 51 },
|
|
3808
|
+
() => (0, import_react12.createElement)(GeneralUpdateRow)
|
|
3809
|
+
)
|
|
3810
|
+
)
|
|
3811
|
+
),
|
|
3812
|
+
"xrxs-plugins-store.settings.general-update"
|
|
3813
|
+
);
|
|
3675
3814
|
ctx.effect(
|
|
3676
3815
|
() => slots.inject(
|
|
3677
3816
|
"plugins.bundle.config",
|
|
@@ -3679,7 +3818,7 @@ function apply(ctx) {
|
|
|
3679
3818
|
"plugins.bundle.config",
|
|
3680
3819
|
() => slots.register(
|
|
3681
3820
|
{ name: "plugins.bundle.config", key: "@xrxs-ai/dsh-xrxs-plugins-store" },
|
|
3682
|
-
(props) => (0,
|
|
3821
|
+
(props) => (0, import_react12.createElement)(BundleConfigSeat, props)
|
|
3683
3822
|
)
|
|
3684
3823
|
)
|
|
3685
3824
|
),
|
package/lib/host/routes.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { ApiError } from "./api-client.js";
|
|
2
2
|
import { approveProfileBuilds } from "./build-approval.js";
|
|
3
|
+
import { applySelfUpdate, checkSelfUpdate } from "./self-update.js";
|
|
3
4
|
import { submitPackage } from "./package-submit.js";
|
|
4
5
|
import { installPlugin, listInstalledFromProfile, rawOutputOf, reportInstall, scrubCredentials, uninstallPlugin, InstallFailure, } from "./installer.js";
|
|
5
6
|
/**
|
|
@@ -393,6 +394,18 @@ async function handleUninstall(rc) {
|
|
|
393
394
|
throw new HttpError(400, '缺少参数 name');
|
|
394
395
|
writeInstallOutcome(rc, await uninstallPlugin(name, rc.deps.installer));
|
|
395
396
|
}
|
|
397
|
+
/** GET /api/self-update:当前版本 vs npm latest(公开端点,无鉴权;失败 latest=null,UI 显示检查失败)。 */
|
|
398
|
+
async function handleSelfUpdateCheck(rc) {
|
|
399
|
+
writeOk(rc, await checkSelfUpdate());
|
|
400
|
+
}
|
|
401
|
+
/** POST /api/self-update/apply:一键自我更新(复用安装链路装 npm 确切版本),成功后提示重启。 */
|
|
402
|
+
async function handleSelfUpdateApply(rc) {
|
|
403
|
+
const body = await readJsonBody(rc.req);
|
|
404
|
+
const version = requiredString(body.version);
|
|
405
|
+
if (version === null)
|
|
406
|
+
throw new HttpError(400, '缺少参数 version(要更新到的确切版本)');
|
|
407
|
+
writeInstallOutcome(rc, await applySelfUpdate(version, rc.deps.installer));
|
|
408
|
+
}
|
|
396
409
|
/** POST /api/approve-builds:批准 pnpm 拦下的依赖构建脚本(allowBuilds 占位 → true),批准后 client 重试安装。 */
|
|
397
410
|
async function handleApproveBuilds(rc) {
|
|
398
411
|
const body = await readJsonBody(rc.req);
|
|
@@ -510,6 +523,8 @@ const ROUTES = [
|
|
|
510
523
|
{ method: 'POST', pattern: '/api/uninstall', handler: handleUninstall },
|
|
511
524
|
{ method: 'POST', pattern: '/api/install-report', handler: handleInstallReport },
|
|
512
525
|
{ method: 'POST', pattern: '/api/approve-builds', handler: handleApproveBuilds },
|
|
526
|
+
{ method: 'GET', pattern: '/api/self-update', handler: handleSelfUpdateCheck },
|
|
527
|
+
{ method: 'POST', pattern: '/api/self-update/apply', handler: handleSelfUpdateApply },
|
|
513
528
|
{
|
|
514
529
|
method: 'POST',
|
|
515
530
|
pattern: '/api/developer/plugins',
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { type InstallOutcome, type InstallerDeps } from './installer.ts';
|
|
2
|
+
/**
|
|
3
|
+
* 市场插件自我更新(设计文档外新增,入口:设置 → 通用 → 薪人薪事市场 下方):
|
|
4
|
+
* - 当前版本:读插件包根的 package.json(运行实例的 version,重启后即新版本);
|
|
5
|
+
* - 最新版本:查 npm registry 的 dist-tag latest(公开端点,无鉴权、零凭据);
|
|
6
|
+
* - 一键更新:复用安装链路 `dsh plugin --profile <n> add @xrxs-ai/dsh-xrxs-plugins-store@<版本>`
|
|
7
|
+
* (installPlugin 白名单 + --profile + 老 CLI 回退全继承)。
|
|
8
|
+
* 覆盖安装运行中的自身目录是安全的:文件句柄仍指向旧 inode,重启后加载新版本 ——
|
|
9
|
+
* 与市场里更新其他插件走的是同一条路。
|
|
10
|
+
*/
|
|
11
|
+
/** npm 包名(自我更新的目标就是本插件)。 */
|
|
12
|
+
export declare const SELF_PACKAGE_NAME = "@xrxs-ai/dsh-xrxs-plugins-store";
|
|
13
|
+
/** 当前运行实例的版本(包根 package.json);读不到为 null(极端情况:裸文件运行)。
|
|
14
|
+
* 依次尝试:模块相对层级(tsc 产物 lib/host/ 与源码 src/host/ 都是上一级的上一级)、
|
|
15
|
+
* 按包名 resolve(profile 安装布局变化时的兜底);读到的包名必须匹配,防止层级猜错读成别的包。 */
|
|
16
|
+
export declare function currentSelfVersion(): string | null;
|
|
17
|
+
export interface SelfUpdateCheck {
|
|
18
|
+
/** 当前运行实例版本;读不到为 null(无法比较,不可更新) */
|
|
19
|
+
current: string | null;
|
|
20
|
+
/** npm 上 latest 的版本;查询失败为 null */
|
|
21
|
+
latest: string | null;
|
|
22
|
+
updateAvailable: boolean;
|
|
23
|
+
}
|
|
24
|
+
/** 查 npm latest 并与当前版本比较;registry 查询失败时 latest 为 null(不抛,便于 UI 展示「检查失败」)。 */
|
|
25
|
+
export declare function checkSelfUpdate(): Promise<SelfUpdateCheck>;
|
|
26
|
+
/** 应用更新:复用安装链路装确切版本(不装 latest tag,避免「检查到装的」漂移)。 */
|
|
27
|
+
export declare function applySelfUpdate(version: string, deps: InstallerDeps): Promise<InstallOutcome>;
|
|
28
|
+
/** 简易 semver 比较(本插件只用 x.y.z;逐段数值比较,长度不齐补 0):
|
|
29
|
+
* a>b → 1,a<b → -1,相等 → 0;不可解析的段当 0。 */
|
|
30
|
+
export declare function compareVersions(a: string, b: string): number;
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { createRequire } from 'node:module';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
import { installPlugin } from "./installer.js";
|
|
5
|
+
/**
|
|
6
|
+
* 市场插件自我更新(设计文档外新增,入口:设置 → 通用 → 薪人薪事市场 下方):
|
|
7
|
+
* - 当前版本:读插件包根的 package.json(运行实例的 version,重启后即新版本);
|
|
8
|
+
* - 最新版本:查 npm registry 的 dist-tag latest(公开端点,无鉴权、零凭据);
|
|
9
|
+
* - 一键更新:复用安装链路 `dsh plugin --profile <n> add @xrxs-ai/dsh-xrxs-plugins-store@<版本>`
|
|
10
|
+
* (installPlugin 白名单 + --profile + 老 CLI 回退全继承)。
|
|
11
|
+
* 覆盖安装运行中的自身目录是安全的:文件句柄仍指向旧 inode,重启后加载新版本 ——
|
|
12
|
+
* 与市场里更新其他插件走的是同一条路。
|
|
13
|
+
*/
|
|
14
|
+
/** npm 包名(自我更新的目标就是本插件)。 */
|
|
15
|
+
export const SELF_PACKAGE_NAME = '@xrxs-ai/dsh-xrxs-plugins-store';
|
|
16
|
+
/** 查 latest 的 registry(源头以 npmjs 为准;镜像站有分钟级同步延迟,可能查到旧 latest)。 */
|
|
17
|
+
const NPM_REGISTRY_LATEST = 'https://registry.npmjs.org/@xrxs-ai%2Fdsh-xrxs-plugins-store/latest';
|
|
18
|
+
/** registry 查询超时:公开端点,给短超时,失败让用户重试而不是干等。 */
|
|
19
|
+
const LATEST_TIMEOUT_MS = 10_000;
|
|
20
|
+
let cachedVersion = null;
|
|
21
|
+
/** 当前运行实例的版本(包根 package.json);读不到为 null(极端情况:裸文件运行)。
|
|
22
|
+
* 依次尝试:模块相对层级(tsc 产物 lib/host/ 与源码 src/host/ 都是上一级的上一级)、
|
|
23
|
+
* 按包名 resolve(profile 安装布局变化时的兜底);读到的包名必须匹配,防止层级猜错读成别的包。 */
|
|
24
|
+
export function currentSelfVersion() {
|
|
25
|
+
if (cachedVersion !== null)
|
|
26
|
+
return cachedVersion;
|
|
27
|
+
const tryRead = (path) => {
|
|
28
|
+
try {
|
|
29
|
+
const pkg = JSON.parse(readFileSync(path, 'utf8'));
|
|
30
|
+
return typeof pkg.version === 'string' && pkg.name === SELF_PACKAGE_NAME ? pkg.version : null;
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
try {
|
|
37
|
+
const pkgPath = fileURLToPath(new URL('../../package.json', import.meta.url));
|
|
38
|
+
cachedVersion = tryRead(pkgPath);
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
cachedVersion = null;
|
|
42
|
+
}
|
|
43
|
+
if (cachedVersion === null) {
|
|
44
|
+
try {
|
|
45
|
+
cachedVersion = tryRead(createRequire(import.meta.url).resolve(`${SELF_PACKAGE_NAME}/package.json`));
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
cachedVersion = null;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return cachedVersion;
|
|
52
|
+
}
|
|
53
|
+
/** 查 npm latest 并与当前版本比较;registry 查询失败时 latest 为 null(不抛,便于 UI 展示「检查失败」)。 */
|
|
54
|
+
export async function checkSelfUpdate() {
|
|
55
|
+
const current = currentSelfVersion();
|
|
56
|
+
let latest = null;
|
|
57
|
+
try {
|
|
58
|
+
const response = await fetch(NPM_REGISTRY_LATEST, {
|
|
59
|
+
signal: AbortSignal.timeout(LATEST_TIMEOUT_MS),
|
|
60
|
+
headers: { accept: 'application/json' },
|
|
61
|
+
});
|
|
62
|
+
if (response.ok) {
|
|
63
|
+
const data = (await response.json());
|
|
64
|
+
if (typeof data.version === 'string' && data.version !== '')
|
|
65
|
+
latest = data.version;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
// 网络失败/超时:latest 保持 null
|
|
70
|
+
}
|
|
71
|
+
return { current, latest, updateAvailable: current !== null && latest !== null && compareVersions(latest, current) > 0 };
|
|
72
|
+
}
|
|
73
|
+
/** 应用更新:复用安装链路装确切版本(不装 latest tag,避免「检查到装的」漂移)。 */
|
|
74
|
+
export async function applySelfUpdate(version, deps) {
|
|
75
|
+
return installPlugin({ name: SELF_PACKAGE_NAME, version, sourceType: 'npm' }, deps);
|
|
76
|
+
}
|
|
77
|
+
/** 简易 semver 比较(本插件只用 x.y.z;逐段数值比较,长度不齐补 0):
|
|
78
|
+
* a>b → 1,a<b → -1,相等 → 0;不可解析的段当 0。 */
|
|
79
|
+
export function compareVersions(a, b) {
|
|
80
|
+
const parse = (text) => text
|
|
81
|
+
.trim()
|
|
82
|
+
.replace(/^v/, '')
|
|
83
|
+
.split('.')
|
|
84
|
+
.map((part) => {
|
|
85
|
+
const numeric = /^\d+/.exec(part);
|
|
86
|
+
return numeric === null ? 0 : Number(numeric[0]);
|
|
87
|
+
});
|
|
88
|
+
const left = parse(a);
|
|
89
|
+
const right = parse(b);
|
|
90
|
+
const length = Math.max(left.length, right.length);
|
|
91
|
+
for (let index = 0; index < length; index += 1) {
|
|
92
|
+
const diff = (left[index] ?? 0) - (right[index] ?? 0);
|
|
93
|
+
if (diff !== 0)
|
|
94
|
+
return diff > 0 ? 1 : -1;
|
|
95
|
+
}
|
|
96
|
+
return 0;
|
|
97
|
+
}
|