@xrxs-ai/dsh-xrxs-plugins-store 0.2.5 → 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 +79 -52
- package/lib/host/installer.d.ts +3 -0
- package/lib/host/installer.js +13 -2
- package/lib/host/routes.js +8 -1
- 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
|
] })
|
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
|
};
|