@max-null/dsh-plugin-center 0.2.2 → 0.2.4
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.js +294 -76
- package/dist/engine.d.ts +16 -4
- package/dist/engine.js +63 -17
- package/dist/market.js +4 -1
- package/dist/rpc.js +9 -1
- package/dist/update.d.ts +24 -0
- package/dist/update.js +80 -1
- package/package.json +2 -2
package/client.js
CHANGED
|
@@ -39,35 +39,35 @@ var CSS = `
|
|
|
39
39
|
.pc-head { display: flex; align-items: center; gap: 10px; }
|
|
40
40
|
.pc-head .pc-sub { margin-top: 0; }
|
|
41
41
|
.pc-head .pc-btn { flex: none; }
|
|
42
|
-
.pc-count { display: inline-block; min-width: 16px; padding: 0 5px; margin-left: 6px; border-radius:
|
|
42
|
+
.pc-count { display: inline-block; min-width: 16px; padding: 0 5px; margin-left: 6px; border-radius: 999px; background: var(--dsw-alias-bg-module-platform); color: var(--dsw-alias-label-secondary); font-size: 11px; font-weight: 500; line-height: 17px; text-align: center; }
|
|
43
43
|
.pc-tab.active .pc-count { background: var(--dsw-alias-state-business-tertiary); color: var(--dsw-alias-state-business-primary); }
|
|
44
|
-
.pc-tabs { display: flex; gap:
|
|
45
|
-
.pc-tab { padding:
|
|
46
|
-
.pc-tab:hover { color: var(--dsw-alias-label-
|
|
47
|
-
.pc-tab.active { color: var(--dsw-alias-label-primary);
|
|
44
|
+
.pc-tabs { display: flex; gap: 22px; align-items: flex-end; margin: 16px 0 12px; border-bottom: 1px solid var(--dsw-alias-border-l2); }
|
|
45
|
+
.pc-tab { padding: 7px 1px 9px; font-size: 13px; line-height: 20px; cursor: pointer; background: none; border: none; font-family: inherit; color: var(--dsw-alias-label-tertiary); position: relative; }
|
|
46
|
+
.pc-tab:hover { color: var(--dsw-alias-label-primary); }
|
|
47
|
+
.pc-tab.active { color: var(--dsw-alias-label-primary); }
|
|
48
|
+
.pc-tab.active::after { content: ''; position: absolute; left: 0; right: 0; bottom: -1px; height: 2px; border-radius: 2px 2px 0 0; background: var(--dsw-alias-label-primary); }
|
|
49
|
+
.pc-tab:focus-visible { outline: 2px solid var(--dsw-alias-state-business-primary); outline-offset: 2px; border-radius: 2px; }
|
|
48
50
|
|
|
49
|
-
.pc-card { border: 1px solid var(--dsw-alias-border-l2); border-radius:
|
|
50
|
-
.pc-card:hover { border-color: var(--dsw-alias-
|
|
51
|
-
.pc-name { font-weight:
|
|
51
|
+
.pc-card { border: 1px solid var(--dsw-alias-border-l2); border-radius: 12px; padding: 14px 16px; margin-bottom: 10px; background: var(--dsw-alias-bg-layer-3); min-width: 0; transition: border-color .16s, background .16s; display: flex; flex-direction: column; }
|
|
52
|
+
.pc-card:hover { border-color: var(--dsw-alias-label-dimmed); }
|
|
53
|
+
.pc-name { font-size: 15px; font-weight: 600; line-height: 1.4; color: var(--dsw-alias-label-primary); }
|
|
52
54
|
.pc-ver { color: var(--dsw-alias-label-caption); font-size: 12px; }
|
|
53
|
-
.pc-desc { color: var(--dsw-alias-label-
|
|
55
|
+
.pc-desc { color: var(--dsw-alias-label-tertiary); font-size: 13px; margin-top: 6px; word-break: break-word; overflow-wrap: break-word; }
|
|
54
56
|
.pc-row { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
|
|
55
57
|
.pc-spacer { flex: 1; }
|
|
56
58
|
.pc-meta { display: flex; gap: 6px; flex-wrap: wrap; align-items: center; margin-top: auto; padding-top: 10px; }
|
|
57
59
|
|
|
58
|
-
.pc-badge { display: inline-flex; align-items: center;
|
|
60
|
+
.pc-badge { display: inline-flex; align-items: center; padding: 1px 8px; border-radius: 999px; font-size: 11px; font-weight: 500; line-height: 17px; white-space: nowrap; }
|
|
59
61
|
.pc-badge.official { background: var(--dsw-alias-state-business-tertiary); color: var(--dsw-alias-state-business-primary); }
|
|
60
62
|
.pc-badge.installed { background: var(--dsw-alias-state-warn-tertiary); color: var(--dsw-alias-state-warn-primary); }
|
|
61
63
|
.pc-badge.local, .pc-badge.builtin { background: var(--dsw-alias-bg-module-platform); color: var(--dsw-alias-label-tertiary); }
|
|
62
|
-
.pc-tag { display: inline-flex; align-items: center;
|
|
64
|
+
.pc-tag { display: inline-flex; align-items: center; padding: 1px 8px; border-radius: 999px; font-size: 11px; font-weight: 500; line-height: 17px; white-space: nowrap; background: var(--dsw-alias-bg-module-platform); color: var(--dsw-alias-label-secondary); }
|
|
63
65
|
.pc-tag.danger { background: var(--dsw-alias-interactive-bg-hover-danger); color: var(--dsw-alias-state-error-primary); }
|
|
64
|
-
.pc-switch { position: relative; flex: none; width:
|
|
65
|
-
.pc-switch::after { content: ''; position: absolute; top:
|
|
66
|
-
.pc-switch.on { background: var(--dsw-alias-state-
|
|
67
|
-
.pc-switch.on::after { left:
|
|
66
|
+
.pc-switch { position: relative; flex: none; width: 40px; height: 22px; border-radius: 11px; border: none; background: var(--dsw-alias-border-l4, rgba(0,0,0,.16)); cursor: pointer; transition: background .15s ease; padding: 0; }
|
|
67
|
+
.pc-switch::after { content: ''; position: absolute; top: 3px; left: 3px; width: 16px; height: 16px; border-radius: 50%; background: #fff; transition: left .15s ease; }
|
|
68
|
+
.pc-switch.on { background: var(--dsw-alias-state-business-primary, #4FC3F7); }
|
|
69
|
+
.pc-switch.on::after { left: 21px; }
|
|
68
70
|
.pc-switch:disabled { opacity: .6; cursor: default; }
|
|
69
|
-
body[data-ds-dark-theme] .pc-switch { background: rgba(255,255,255,.28); }
|
|
70
|
-
body[data-ds-dark-theme] .pc-switch.on { background: var(--dsw-alias-state-success-primary); }
|
|
71
71
|
|
|
72
72
|
.pc-toolbar { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; padding: 4px 0; }
|
|
73
73
|
/* \u7B5B\u9009\u533A\u7EB5\u5411\u5BB9\u5668\uFF1AAI \u63A8\u8350\u884C / \u4E3B\u7B5B\u9009\u884C / \u5206\u7C7B\u884C\u4E09\u6BB5\uFF0C\u95F4\u8DDD\u7EDF\u4E00\u3002 */
|
|
@@ -81,27 +81,27 @@ body[data-ds-dark-theme] .pc-switch.on { background: var(--dsw-alias-state-succe
|
|
|
81
81
|
.pc-limit { margin: 2px 0 8px; font-size: 12px; line-height: 16px; color: var(--dsw-alias-label-caption); }
|
|
82
82
|
.pc-ai-row { display: flex; gap: 8px; align-items: center; }
|
|
83
83
|
.pc-ai-row .pc-btn { height: 28px; }
|
|
84
|
-
.pc-chip { height: 28px; padding: 0 12px; border-radius:
|
|
84
|
+
.pc-chip { height: 28px; padding: 0 12px; border-radius: 14px; border: 1px solid var(--dsw-alias-border-l2); background: transparent; color: var(--dsw-alias-label-secondary); font-size: 12px; line-height: 18px; cursor: pointer; font-family: inherit; transition: background .15s ease, color .15s ease, border-color .15s ease; }
|
|
85
85
|
.pc-chip:hover { background: var(--dsw-alias-interactive-bg-hover); }
|
|
86
|
-
.pc-chip.active { background: var(--dsw-alias-
|
|
86
|
+
.pc-chip.active { background: var(--dsw-alias-label-primary); border-color: var(--dsw-alias-label-primary); color: var(--dsw-alias-bg-layer-3); }
|
|
87
87
|
|
|
88
|
-
.pc-btn {
|
|
88
|
+
.pc-btn { padding: 5px 14px; border-radius: 8px; border: 1px solid var(--dsw-alias-border-l2); background: transparent; color: var(--dsw-alias-label-primary); font-size: 13px; line-height: 1.5; cursor: pointer; font-family: inherit; }
|
|
89
89
|
.pc-btn:hover { background: var(--dsw-alias-interactive-bg-hover); }
|
|
90
|
-
.pc-btn.primary { border: none; background: var(--dsw-alias-button-primary-fill); color: var(--dsw-alias-
|
|
90
|
+
.pc-btn.primary { border: none; background: var(--dsw-alias-button-primary-fill); color: var(--dsw-alias-label-primary-foreground); }
|
|
91
91
|
.pc-btn.primary:hover { background: var(--dsw-alias-button-primary-hover); }
|
|
92
92
|
.pc-btn:disabled { opacity: 0.5; cursor: default; }
|
|
93
93
|
|
|
94
|
-
.pc-search { height:
|
|
95
|
-
.pc-search:focus { border-color: var(--dsw-alias-
|
|
96
|
-
.pc-search::placeholder { color: var(--dsw-alias-label-
|
|
94
|
+
.pc-search { height: 34px; padding: 0 12px; border-radius: 8px; border: 1px solid var(--dsw-alias-border-l2); background: var(--dsw-alias-bg-layer-3); color: var(--dsw-alias-label-primary); font-size: 13px; line-height: 1.5; outline: none; width: 200px; font-family: inherit; }
|
|
95
|
+
.pc-search:focus { border-color: var(--dsw-alias-brand-primary); }
|
|
96
|
+
.pc-search::placeholder { color: var(--dsw-alias-label-dimmed); }
|
|
97
97
|
/* \u5B9A\u5236 select\uFF1A\u5265\u79BB\u6D4F\u89C8\u5668\u539F\u751F\u5916\u89C2\uFF08\u9AD8\u5EA6/\u5185\u8FB9\u8DDD/padding \u5DEE\u5F02\u662F\u8DE8\u63A7\u4EF6\u4E0D\u9F50\u7684
|
|
98
|
-
\u4E3B\u56E0\uFF09\uFF0C\u7528\u80CC\u666F chevron \u66FF\u4EE3\u7CFB\u7EDF\u7BAD\u5934\uFF0C\u4E0E
|
|
99
|
-
.pc-select { height:
|
|
98
|
+
\u4E3B\u56E0\uFF09\uFF0C\u7528\u80CC\u666F chevron \u66FF\u4EE3\u7CFB\u7EDF\u7BAD\u5934\uFF0C\u4E0E\u8F93\u5165\u6846\u540C\u9AD8\u540C\u5706\u89D2\u3002 */
|
|
99
|
+
.pc-select { height: 34px; padding: 0 26px 0 12px; border-radius: 8px; border: 1px solid var(--dsw-alias-border-l2); background-color: var(--dsw-alias-bg-layer-3); color: var(--dsw-alias-label-secondary); font-size: 13px; line-height: 1.5; cursor: pointer; font-family: inherit; appearance: none; -webkit-appearance: none; background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='10' viewBox='0 0 10 10' fill='none' stroke='%23777777' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M2 3.5l3 3 3-3'/%3E%3C/svg%3E"); background-repeat: no-repeat; background-position: right 10px center; background-size: 10px 10px; }
|
|
100
100
|
.pc-select:hover { background-color: var(--dsw-alias-interactive-bg-hover); }
|
|
101
|
-
.pc-select:focus-visible { outline: none; border-color: var(--dsw-alias-
|
|
101
|
+
.pc-select:focus-visible { outline: none; border-color: var(--dsw-alias-brand-primary); }
|
|
102
102
|
body[data-ds-dark-theme] .pc-select { background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='10' viewBox='0 0 10 10' fill='none' stroke='%23b0b0b0' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M2 3.5l3 3 3-3'/%3E%3C/svg%3E"); }
|
|
103
|
-
.pc-iconbtn { width: 28px; height: 28px; padding:
|
|
104
|
-
.pc-iconbtn:hover { background: var(--dsw-alias-
|
|
103
|
+
.pc-iconbtn { width: 28px; height: 28px; padding: 6px; border-radius: 7px; border: none; background: transparent; color: var(--dsw-alias-label-tertiary); cursor: pointer; display: inline-flex; align-items: center; justify-content: center; }
|
|
104
|
+
.pc-iconbtn:hover { background: var(--dsw-alias-bg-layer-1); color: var(--dsw-alias-label-primary); }
|
|
105
105
|
|
|
106
106
|
.pc-grid { display: grid; gap: 12px; }
|
|
107
107
|
.pc-grid.double { grid-template-columns: 1fr 1fr; }
|
|
@@ -261,6 +261,28 @@ function usePendingVersion() {
|
|
|
261
261
|
}, []);
|
|
262
262
|
return v;
|
|
263
263
|
}
|
|
264
|
+
var doneUpdatesStore = [];
|
|
265
|
+
var doneUpdatesVersion = 0;
|
|
266
|
+
var doneUpdatesListeners = /* @__PURE__ */ new Set();
|
|
267
|
+
function markDoneUpdate(entry) {
|
|
268
|
+
if (doneUpdatesStore.some((d) => d.name === entry.name)) return;
|
|
269
|
+
doneUpdatesStore.push(entry);
|
|
270
|
+
doneUpdatesVersion++;
|
|
271
|
+
doneUpdatesListeners.forEach((l) => l());
|
|
272
|
+
}
|
|
273
|
+
function useDoneUpdatesVersion() {
|
|
274
|
+
const [v, setV] = (0, import_react.useState)(0);
|
|
275
|
+
(0, import_react.useEffect)(() => {
|
|
276
|
+
const l = () => {
|
|
277
|
+
setV((x) => x + 1);
|
|
278
|
+
};
|
|
279
|
+
doneUpdatesListeners.add(l);
|
|
280
|
+
return () => {
|
|
281
|
+
doneUpdatesListeners.delete(l);
|
|
282
|
+
};
|
|
283
|
+
}, []);
|
|
284
|
+
return v;
|
|
285
|
+
}
|
|
264
286
|
var installedListeners = /* @__PURE__ */ new Set();
|
|
265
287
|
function useInstalledVersion() {
|
|
266
288
|
const [v, setV] = (0, import_react.useState)(0);
|
|
@@ -384,10 +406,21 @@ var STRINGS = {
|
|
|
384
406
|
installQueued: "\u5DF2\u53D1\u8D77\u5B89\u88C5 {n}\uFF0C\u91CD\u542F dsh web \u540E\u751F\u6548",
|
|
385
407
|
installFailed: "\u5B89\u88C5\u5931\u8D25\uFF1A{e}",
|
|
386
408
|
installNotApplied: "\u5B89\u88C5\u672A\u751F\u6548",
|
|
387
|
-
updatedOne: "\u5DF2\u66F4\u65B0 {n}\uFF0C\u91CD\u542F
|
|
409
|
+
updatedOne: "\u5DF2\u66F4\u65B0 {n}\uFF0C\u91CD\u542F\u540E\u751F\u6548",
|
|
388
410
|
updateFailed: "\u66F4\u65B0\u5931\u8D25\uFF1A{e}",
|
|
389
411
|
updateNotApplied: "\u66F4\u65B0\u672A\u751F\u6548",
|
|
390
|
-
updatedMany: "\u5DF2\u66F4\u65B0 {n} \u4E2A\u63D2\u4EF6\uFF0C\u91CD\u542F
|
|
412
|
+
updatedMany: "\u5DF2\u66F4\u65B0 {n} \u4E2A\u63D2\u4EF6\uFF0C\u91CD\u542F\u540E\u751F\u6548",
|
|
413
|
+
commandTitle: "\u5728\u7EC8\u7AEF\u6267\u884C\u4EE5\u4E0B\u547D\u4EE4",
|
|
414
|
+
commandHint: "\u5F53\u524D\u5E94\u7528\u6B63\u5728\u8FD0\u884C\uFF0C\u88AB\u9501\u5B9A\u7684\u6587\u4EF6\u65E0\u6CD5\u5728\u5E94\u7528\u5185\u66FF\u6362\u3002\u8BF7\u5148\u5173\u95ED\u5E94\u7528\uFF0C\u518D\u5728\u7EC8\u7AEF\u6267\u884C\uFF1A",
|
|
415
|
+
commandCopy: "\u590D\u5236\u547D\u4EE4",
|
|
416
|
+
commandCopied: "\u5DF2\u590D\u5236",
|
|
417
|
+
updateRestartNow: "\u5DF2\u4E0B\u8F7D {n} \u4E2A\u66F4\u65B0\uFF0C\u91CD\u542F\u601D\u7075\u540E\u81EA\u52A8\u5B89\u88C5\u3002\u7ACB\u5373\u91CD\u542F\uFF1F",
|
|
418
|
+
updateRestartBusy: "\u6709 {n} \u4E2A\u4F1A\u8BDD\u6B63\u5728\u8FDB\u884C\u4E2D\uFF0C\u672A\u6267\u884C\u91CD\u542F\uFF1B\u66F4\u65B0\u5DF2\u51C6\u5907\u597D\uFF0C\u7A0D\u540E\u624B\u52A8\u91CD\u542F\u5373\u53EF",
|
|
419
|
+
updatedPendingTag: "\u5DF2\u66F4\u65B0\u5F85\u91CD\u542F",
|
|
420
|
+
restartUnavailable: "\u5F53\u524D\u73AF\u5883\u4E0D\u652F\u6301\u81EA\u52A8\u91CD\u542F\uFF0C\u8BF7\u624B\u52A8\u91CD\u542F\u5E94\u7528",
|
|
421
|
+
restartAskTitle: "\u9700\u8981\u91CD\u542F\u751F\u6548",
|
|
422
|
+
restartAskBody: "\u5DF2\u66F4\u65B0 {n} \u4E2A\u63D2\u4EF6\uFF0C\u91CD\u542F\u601D\u7075\u540E\u751F\u6548\uFF08\u6709\u8FDB\u884C\u4E2D\u7684\u4F1A\u8BDD\u65F6\u4F1A\u5148\u68C0\u67E5\uFF09",
|
|
423
|
+
restartNowBtn: "\u7ACB\u5373\u91CD\u542F",
|
|
391
424
|
updateSummary: "\u66F4\u65B0\u5B8C\u6210\uFF1A\u6210\u529F {a}\uFF0C\u5931\u8D25 {b}\uFF08{c}\uFF09",
|
|
392
425
|
whatsNewTitle: "\u63D2\u4EF6\u66F4\u65B0",
|
|
393
426
|
whatsNewSub: "{n} \u4E2A\u63D2\u4EF6\u6709\u65B0\u7248\u672C",
|
|
@@ -458,10 +491,21 @@ var STRINGS = {
|
|
|
458
491
|
installQueued: "Install of {n} started; restart dsh web to take effect",
|
|
459
492
|
installFailed: "Install failed: {e}",
|
|
460
493
|
installNotApplied: "Install did not take effect",
|
|
461
|
-
updatedOne: "Updated {n}; restart
|
|
494
|
+
updatedOne: "Updated {n}; restart to take effect",
|
|
462
495
|
updateFailed: "Update failed: {e}",
|
|
463
496
|
updateNotApplied: "Update did not take effect",
|
|
464
|
-
updatedMany: "Updated {n}
|
|
497
|
+
updatedMany: "Updated {n} plugin(s); restart to take effect",
|
|
498
|
+
commandTitle: "Run this command in a terminal",
|
|
499
|
+
commandHint: "The app is running and locked files cannot be replaced in-place. Close the app first, then run:",
|
|
500
|
+
commandCopy: "Copy command",
|
|
501
|
+
commandCopied: "Copied",
|
|
502
|
+
updateRestartNow: "{n} update(s) downloaded; auto-installs after restarting SSiD. Restart now?",
|
|
503
|
+
updateRestartBusy: "{n} session(s) still in progress \u2014 no restart; updates ready, restart manually later",
|
|
504
|
+
updatedPendingTag: "Updated \u2014 restart pending",
|
|
505
|
+
restartUnavailable: "Auto-restart unavailable here; please restart manually",
|
|
506
|
+
restartAskTitle: "Restart required",
|
|
507
|
+
restartAskBody: "{n} plugin(s) updated; takes effect after restarting SSiD (active sessions are checked first)",
|
|
508
|
+
restartNowBtn: "Restart now",
|
|
465
509
|
updateSummary: "Update done: {a} succeeded, {b} failed ({c})",
|
|
466
510
|
whatsNewTitle: "Plugin updates",
|
|
467
511
|
whatsNewSub: "{n} plugins have new versions",
|
|
@@ -759,28 +803,45 @@ function MarketView({ category, single, source, search, onCount }) {
|
|
|
759
803
|
] }) })
|
|
760
804
|
] });
|
|
761
805
|
}
|
|
762
|
-
function UpdatesView({ updates, refresh, updateOne, busy }) {
|
|
806
|
+
function UpdatesView({ updates, refresh, updateOne, busy, doneUpdates, onDoneClick }) {
|
|
763
807
|
const t = useT();
|
|
764
808
|
useUpdatingVersion();
|
|
765
809
|
if (updates === null) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "pc-sub", children: t("checkingUpdates") });
|
|
766
|
-
|
|
810
|
+
const doneOnly = doneUpdates.filter((d) => !(updates ?? []).some((u) => u.name === d.name));
|
|
811
|
+
if (updates.length === 0 && doneOnly.length === 0) return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
|
|
767
812
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "pc-sub", children: t("noUpdates") }),
|
|
768
813
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: "pc-btn", onClick: refresh, children: t("recheck") })
|
|
769
814
|
] });
|
|
770
|
-
return /* @__PURE__ */ (0, import_jsx_runtime.
|
|
771
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "pc-
|
|
772
|
-
/* @__PURE__ */ (0, import_jsx_runtime.
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
815
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
|
|
816
|
+
updates.map((u) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "pc-card", children: [
|
|
817
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "pc-row", children: [
|
|
818
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "pc-name", children: u.name }),
|
|
819
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "pc-ver", children: u.fromVersion }),
|
|
820
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "pc-ver", children: "\u2192" }),
|
|
821
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { color: "var(--dsw-alias-state-business-primary)", fontWeight: 500 }, children: u.toVersion }),
|
|
822
|
+
u.compat === "incompatible" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "pc-tag danger", children: t("incompat") }),
|
|
823
|
+
pendingInstall.has(u.name) && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "pc-tag", children: t("pendingRestart") }),
|
|
824
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "pc-spacer" }),
|
|
825
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: "pc-btn primary", disabled: busy !== null || pendingInstall.has(u.name), onClick: () => {
|
|
826
|
+
updateOne(u.name, u.toVersion);
|
|
827
|
+
}, children: busy === u.name || busy === "__all__" || updatingPlugins.has(u.name) ? t("updating") : t("update") })
|
|
828
|
+
] }),
|
|
829
|
+
u.changelog.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ul", { className: "pc-wn-list", children: u.changelog.slice(0, 5).map((line, i) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("li", { children: line }, i)) })
|
|
830
|
+
] }, u.name)),
|
|
831
|
+
doneOnly.map((d) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "pc-card", children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "pc-row", children: [
|
|
832
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "pc-name", children: d.name }),
|
|
833
|
+
d.fromVersion !== "" && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
|
|
834
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "pc-ver", children: d.fromVersion }),
|
|
835
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "pc-ver", children: "\u2192" })
|
|
836
|
+
] }),
|
|
837
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { color: "var(--dsw-alias-state-business-primary)", fontWeight: 500 }, children: d.toVersion }),
|
|
838
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "pc-tag", children: t("updatedPendingTag") }),
|
|
777
839
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "pc-spacer" }),
|
|
778
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: "pc-btn primary",
|
|
779
|
-
|
|
780
|
-
}, children:
|
|
781
|
-
] }),
|
|
782
|
-
|
|
783
|
-
] }, u.name)) });
|
|
840
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: "pc-btn primary", onClick: () => {
|
|
841
|
+
onDoneClick(d.name);
|
|
842
|
+
}, children: t("updatedPendingTag") })
|
|
843
|
+
] }) }, d.name))
|
|
844
|
+
] });
|
|
784
845
|
}
|
|
785
846
|
function DiagnoseView() {
|
|
786
847
|
const t = useT();
|
|
@@ -872,6 +933,63 @@ function DiagnoseView() {
|
|
|
872
933
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("pre", { className: "pc-desc", style: { whiteSpace: "pre-wrap", fontSize: 11 }, children: report.pnpmLogTail === "" ? "\u2014" : report.pnpmLogTail })
|
|
873
934
|
] });
|
|
874
935
|
}
|
|
936
|
+
function CommandDialog({ command, copied, onCopy, onClose }) {
|
|
937
|
+
const t = useT();
|
|
938
|
+
return (0, import_react_dom.createPortal)(
|
|
939
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { position: "fixed", inset: 0, background: "rgba(0,0,0,.55)", display: "flex", alignItems: "center", justifyContent: "center", zIndex: 9999 }, children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { width: "min(560px, 92vw)", background: "var(--dsw-alias-bg-layer-3)", border: "1px solid var(--dsw-alias-border-l2)", borderRadius: 12, padding: 14, display: "flex", flexDirection: "column", gap: 10 }, children: [
|
|
940
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { fontSize: 13, fontWeight: 600, color: "var(--dsw-alias-label-primary, #d8e0ea)" }, children: t("commandTitle") }),
|
|
941
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { fontSize: 12, color: "var(--dsw-alias-label-secondary, #67748a)", lineHeight: 1.5 }, children: t("commandHint") }),
|
|
942
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
943
|
+
"textarea",
|
|
944
|
+
{
|
|
945
|
+
readOnly: true,
|
|
946
|
+
value: command,
|
|
947
|
+
rows: Math.min(8, command.split("\n").length + 1),
|
|
948
|
+
style: { width: "100%", boxSizing: "border-box", background: "var(--dsw-alias-bg-module-platform, rgba(128,148,168,.12))", color: "var(--dsw-alias-label-primary, #d8e0ea)", border: "1px solid var(--dsw-alias-border-l2, #1e2836)", borderRadius: 6, padding: "8px 10px", fontSize: 12, fontFamily: "monospace", resize: "vertical" }
|
|
949
|
+
}
|
|
950
|
+
),
|
|
951
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { display: "flex", gap: 8, justifyContent: "flex-end" }, children: [
|
|
952
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "pc-btn", onClick: () => {
|
|
953
|
+
void navigator.clipboard.writeText(command).then(onCopy).catch(() => {
|
|
954
|
+
});
|
|
955
|
+
}, children: copied ? t("commandCopied") : t("commandCopy") }),
|
|
956
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "pc-btn primary", onClick: onClose, children: t("close") })
|
|
957
|
+
] })
|
|
958
|
+
] }) }),
|
|
959
|
+
document.body
|
|
960
|
+
);
|
|
961
|
+
}
|
|
962
|
+
function RestartDialog({ count, onRestart, onClose }) {
|
|
963
|
+
const t = useT();
|
|
964
|
+
return (0, import_react_dom.createPortal)(
|
|
965
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { position: "fixed", inset: 0, background: "rgba(0,0,0,.55)", display: "flex", alignItems: "center", justifyContent: "center", zIndex: 9999 }, children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { width: "min(420px, 92vw)", background: "var(--dsw-alias-bg-layer-3)", border: "1px solid var(--dsw-alias-border-l2)", borderRadius: 12, padding: 14, display: "flex", flexDirection: "column", gap: 10 }, children: [
|
|
966
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { fontSize: 13, fontWeight: 600, color: "var(--dsw-alias-label-primary, #d8e0ea)" }, children: t("restartAskTitle") }),
|
|
967
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { fontSize: 12, color: "var(--dsw-alias-label-secondary, #67748a)", lineHeight: 1.5 }, children: t("restartAskBody", { n: count }) }),
|
|
968
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { display: "flex", gap: 8, justifyContent: "flex-end" }, children: [
|
|
969
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "pc-btn", onClick: onClose, children: t("later") }),
|
|
970
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
971
|
+
"button",
|
|
972
|
+
{
|
|
973
|
+
type: "button",
|
|
974
|
+
style: {
|
|
975
|
+
padding: "3px 12px",
|
|
976
|
+
fontSize: 11.5,
|
|
977
|
+
border: "none",
|
|
978
|
+
borderRadius: 6,
|
|
979
|
+
cursor: "pointer",
|
|
980
|
+
fontWeight: 600,
|
|
981
|
+
background: "var(--dsw-alias-button-primary-fill)",
|
|
982
|
+
color: "var(--dsw-alias-label-primary-foreground)"
|
|
983
|
+
},
|
|
984
|
+
onClick: onRestart,
|
|
985
|
+
children: t("restartNowBtn")
|
|
986
|
+
}
|
|
987
|
+
)
|
|
988
|
+
] })
|
|
989
|
+
] }) }),
|
|
990
|
+
document.body
|
|
991
|
+
);
|
|
992
|
+
}
|
|
875
993
|
function CenterPanel({ variant = "section" }) {
|
|
876
994
|
const t = useT();
|
|
877
995
|
const [view, setView] = (0, import_react.useState)("installed");
|
|
@@ -887,6 +1005,28 @@ function CenterPanel({ variant = "section" }) {
|
|
|
887
1005
|
const [busyUpdate, setBusyUpdate] = (0, import_react.useState)(null);
|
|
888
1006
|
const [checking, setChecking] = (0, import_react.useState)(false);
|
|
889
1007
|
const [togglingId, setTogglingId] = (0, import_react.useState)(null);
|
|
1008
|
+
const [commandDialog, setCommandDialog] = (0, import_react.useState)(null);
|
|
1009
|
+
const [copied, setCopied] = (0, import_react.useState)(false);
|
|
1010
|
+
const readyPending = (0, import_react.useRef)([]);
|
|
1011
|
+
const inUpdateAll = (0, import_react.useRef)(false);
|
|
1012
|
+
useDoneUpdatesVersion();
|
|
1013
|
+
const [restartAsk, setRestartAsk] = (0, import_react.useState)(0);
|
|
1014
|
+
const runRestartNow = (n) => {
|
|
1015
|
+
void fetch("/ssid/api/sessionRoot.restart", { method: "POST", headers: { "content-type": "application/json" }, body: "{}" }).then((res) => res.json()).then((body2) => {
|
|
1016
|
+
if (body2.value?.code === "busy") {
|
|
1017
|
+
showToast(t("updateRestartBusy", { n: body2.value.activeSessions ?? 0 }), "error", 8e3);
|
|
1018
|
+
}
|
|
1019
|
+
}).catch(() => showToast(t("restartUnavailable"), "error", 8e3));
|
|
1020
|
+
};
|
|
1021
|
+
const askRestart = (n) => {
|
|
1022
|
+
setRestartAsk(n);
|
|
1023
|
+
};
|
|
1024
|
+
const flushReady = () => {
|
|
1025
|
+
const n = readyPending.current.length;
|
|
1026
|
+
if (n === 0) return;
|
|
1027
|
+
readyPending.current = [];
|
|
1028
|
+
askRestart(n);
|
|
1029
|
+
};
|
|
890
1030
|
const handleToggle = (p) => {
|
|
891
1031
|
if (togglingId !== null) return;
|
|
892
1032
|
const id = p.entryId;
|
|
@@ -989,11 +1129,29 @@ function CenterPanel({ variant = "section" }) {
|
|
|
989
1129
|
setUpdating(name, true);
|
|
990
1130
|
void rpc("update", { name, version }).then(
|
|
991
1131
|
(v) => {
|
|
992
|
-
const
|
|
993
|
-
if (
|
|
1132
|
+
const value = typeof v === "object" && v !== null ? v : null;
|
|
1133
|
+
if (value === null || value.durationMs === void 0 && value.command === void 0) throw new Error(t("updateNotApplied"));
|
|
994
1134
|
setUpdating(name, false);
|
|
995
1135
|
setBusyUpdate(null);
|
|
996
|
-
|
|
1136
|
+
if (value.command !== void 0 && value.command !== "") {
|
|
1137
|
+
setCommandDialog(value.command);
|
|
1138
|
+
setCopied(false);
|
|
1139
|
+
return;
|
|
1140
|
+
}
|
|
1141
|
+
if (value.pending === true) {
|
|
1142
|
+
pendingInstall.add(name);
|
|
1143
|
+
readyPending.current.push(name);
|
|
1144
|
+
if (!inUpdateAll.current) flushReady();
|
|
1145
|
+
refreshUpdates(true);
|
|
1146
|
+
return;
|
|
1147
|
+
}
|
|
1148
|
+
pendingInstall.add(name);
|
|
1149
|
+
markDoneUpdate({
|
|
1150
|
+
name,
|
|
1151
|
+
fromVersion: updates?.find((u) => u.name === name)?.fromVersion ?? "",
|
|
1152
|
+
toVersion: version
|
|
1153
|
+
});
|
|
1154
|
+
showToast(t("updatedOne", { n: name }) + (value.durationMs !== void 0 ? `\uFF08${(value.durationMs / 1e3).toFixed(1)}s\uFF09` : ""), "ok", 5e3);
|
|
997
1155
|
refreshUpdates(true);
|
|
998
1156
|
},
|
|
999
1157
|
(e) => {
|
|
@@ -1006,6 +1164,9 @@ function CenterPanel({ variant = "section" }) {
|
|
|
1006
1164
|
const updateAll = async () => {
|
|
1007
1165
|
if (updates === null || updates.length === 0) return;
|
|
1008
1166
|
setBusyUpdate("__all__");
|
|
1167
|
+
inUpdateAll.current = true;
|
|
1168
|
+
readyPending.current = [];
|
|
1169
|
+
const commands = [];
|
|
1009
1170
|
let okCount = 0;
|
|
1010
1171
|
const okNames = [];
|
|
1011
1172
|
const failures = [];
|
|
@@ -1013,21 +1174,43 @@ function CenterPanel({ variant = "section" }) {
|
|
|
1013
1174
|
setUpdating(u.name, true);
|
|
1014
1175
|
try {
|
|
1015
1176
|
const v = await rpc("update", { name: u.name, version: u.toVersion });
|
|
1016
|
-
const
|
|
1017
|
-
if (
|
|
1177
|
+
const value = typeof v === "object" && v !== null ? v : null;
|
|
1178
|
+
if (value === null || value.durationMs === void 0 && value.command === void 0) throw new Error(t("updateNotApplied"));
|
|
1018
1179
|
okCount++;
|
|
1019
|
-
|
|
1180
|
+
if (value.command !== void 0 && value.command !== "") {
|
|
1181
|
+
commands.push(value.command);
|
|
1182
|
+
continue;
|
|
1183
|
+
}
|
|
1184
|
+
if (value.pending === true) {
|
|
1185
|
+
readyPending.current.push(u.name);
|
|
1186
|
+
okNames.push(`${u.name}\uFF08\u5F85\u91CD\u542F\uFF09`);
|
|
1187
|
+
continue;
|
|
1188
|
+
}
|
|
1189
|
+
pendingInstall.add(u.name);
|
|
1190
|
+
markDoneUpdate({
|
|
1191
|
+
name: u.name,
|
|
1192
|
+
fromVersion: u.fromVersion,
|
|
1193
|
+
toVersion: u.toVersion
|
|
1194
|
+
});
|
|
1195
|
+
okNames.push(value.durationMs !== void 0 ? `${u.name}\uFF08${(value.durationMs / 1e3).toFixed(1)}s\uFF09` : u.name);
|
|
1020
1196
|
} catch (e) {
|
|
1021
1197
|
failures.push(`${u.name}\uFF1A${e instanceof Error ? e.message : String(e)}`);
|
|
1022
1198
|
} finally {
|
|
1023
1199
|
setUpdating(u.name, false);
|
|
1024
1200
|
}
|
|
1025
1201
|
}
|
|
1202
|
+
inUpdateAll.current = false;
|
|
1026
1203
|
setBusyUpdate(null);
|
|
1027
|
-
if (
|
|
1028
|
-
|
|
1029
|
-
|
|
1204
|
+
if (commands.length > 0) {
|
|
1205
|
+
setCommandDialog(commands.join("\n"));
|
|
1206
|
+
setCopied(false);
|
|
1207
|
+
} else if (readyPending.current.length > 0) {
|
|
1208
|
+
flushReady();
|
|
1209
|
+
}
|
|
1210
|
+
if (failures.length > 0) {
|
|
1030
1211
|
showToast(t("updateSummary", { a: okCount, b: failures.length, c: failures.join("\uFF1B") }), "error", 15e3);
|
|
1212
|
+
} else if (commands.length === 0 && readyPending.current.length === 0) {
|
|
1213
|
+
showToast(t("updatedMany", { n: okCount }) + `\uFF08${okNames.join("\u3001")}\uFF09`, "ok", 6e3);
|
|
1031
1214
|
}
|
|
1032
1215
|
refreshUpdates();
|
|
1033
1216
|
};
|
|
@@ -1089,11 +1272,7 @@ function CenterPanel({ variant = "section" }) {
|
|
|
1089
1272
|
}, children: [
|
|
1090
1273
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "awesome", children: "awesome-dsh-plugin" }),
|
|
1091
1274
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "oh-my-dsh", children: "Oh-My-DSH" }),
|
|
1092
|
-
/* @__PURE__ */ (0, import_jsx_runtime.
|
|
1093
|
-
"dsh-market\uFF08",
|
|
1094
|
-
counts.dshMarket,
|
|
1095
|
-
"\uFF09"
|
|
1096
|
-
] }),
|
|
1275
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "dsh-market", children: "dsh-market" }),
|
|
1097
1276
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "all", children: t("allMarkets") })
|
|
1098
1277
|
] }),
|
|
1099
1278
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "pc-spacer" }),
|
|
@@ -1121,28 +1300,58 @@ function CenterPanel({ variant = "section" }) {
|
|
|
1121
1300
|
const body = /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
|
|
1122
1301
|
view === "installed" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(InstalledView, { search, category: installedCategory, source: installedSource, onToggle: handleToggle, togglingId }),
|
|
1123
1302
|
view === "market" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MarketView, { category, single, source, search: marketSearch, onCount: handleMarketCount }),
|
|
1124
|
-
view === "updates" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(UpdatesView, { updates, refresh: refreshUpdates, updateOne, busy: busyUpdate
|
|
1303
|
+
view === "updates" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(UpdatesView, { updates, refresh: refreshUpdates, updateOne, busy: busyUpdate, doneUpdates: doneUpdatesStore, onDoneClick: () => {
|
|
1304
|
+
askRestart(1);
|
|
1305
|
+
} }),
|
|
1125
1306
|
view === "diagnose" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(DiagnoseView, {})
|
|
1126
1307
|
] });
|
|
1127
1308
|
if (variant === "overlay") {
|
|
1128
|
-
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
1129
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { flex: "
|
|
1130
|
-
|
|
1309
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
|
|
1310
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { display: "flex", flexDirection: "column", flex: 1, minHeight: 0 }, children: [
|
|
1311
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { flex: "none" }, children: [
|
|
1312
|
+
head(false),
|
|
1313
|
+
tabs,
|
|
1314
|
+
installedToolbar,
|
|
1315
|
+
marketToolbar
|
|
1316
|
+
] }),
|
|
1317
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "pc-scroll", children: body })
|
|
1318
|
+
] }),
|
|
1319
|
+
commandDialog !== null && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CommandDialog, { command: commandDialog, copied, onCopy: () => {
|
|
1320
|
+
setCopied(true);
|
|
1321
|
+
}, onClose: () => {
|
|
1322
|
+
setCommandDialog(null);
|
|
1323
|
+
} }),
|
|
1324
|
+
restartAsk > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(RestartDialog, { count: restartAsk, onRestart: () => {
|
|
1325
|
+
const n = restartAsk;
|
|
1326
|
+
setRestartAsk(0);
|
|
1327
|
+
runRestartNow(n);
|
|
1328
|
+
}, onClose: () => {
|
|
1329
|
+
setRestartAsk(0);
|
|
1330
|
+
} })
|
|
1331
|
+
] });
|
|
1332
|
+
}
|
|
1333
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
|
|
1334
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { display: "flex", flexDirection: "column", height: "100%", minHeight: 0 }, children: [
|
|
1335
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { flex: "none", paddingBottom: "4px" }, children: [
|
|
1336
|
+
head(true),
|
|
1131
1337
|
tabs,
|
|
1132
1338
|
installedToolbar,
|
|
1133
1339
|
marketToolbar
|
|
1134
1340
|
] }),
|
|
1135
1341
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "pc-scroll", children: body })
|
|
1136
|
-
] });
|
|
1137
|
-
}
|
|
1138
|
-
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { display: "flex", flexDirection: "column", height: "100%", minHeight: 0 }, children: [
|
|
1139
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { flex: "none", paddingBottom: "4px" }, children: [
|
|
1140
|
-
head(true),
|
|
1141
|
-
tabs,
|
|
1142
|
-
installedToolbar,
|
|
1143
|
-
marketToolbar
|
|
1144
1342
|
] }),
|
|
1145
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1343
|
+
commandDialog !== null && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CommandDialog, { command: commandDialog, copied, onCopy: () => {
|
|
1344
|
+
setCopied(true);
|
|
1345
|
+
}, onClose: () => {
|
|
1346
|
+
setCommandDialog(null);
|
|
1347
|
+
} }),
|
|
1348
|
+
restartAsk > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(RestartDialog, { count: restartAsk, onRestart: () => {
|
|
1349
|
+
const n = restartAsk;
|
|
1350
|
+
setRestartAsk(0);
|
|
1351
|
+
runRestartNow(n);
|
|
1352
|
+
}, onClose: () => {
|
|
1353
|
+
setRestartAsk(0);
|
|
1354
|
+
} })
|
|
1146
1355
|
] });
|
|
1147
1356
|
}
|
|
1148
1357
|
function HeaderButton() {
|
|
@@ -1190,8 +1399,17 @@ function WhatsNewDialog() {
|
|
|
1190
1399
|
for (const u of whatsNewDigests) {
|
|
1191
1400
|
try {
|
|
1192
1401
|
const v = await rpc("update", { name: u.name, version: u.toVersion });
|
|
1193
|
-
|
|
1402
|
+
const value = typeof v === "object" && v !== null ? v : null;
|
|
1403
|
+
if (value === null || value.durationMs === void 0 && value.command === void 0) throw new Error(t("updateNotApplied"));
|
|
1404
|
+
if (value.command !== void 0 && value.command !== "") {
|
|
1405
|
+
failures.push(`${u.name}\uFF1A${t("commandHint")}`);
|
|
1406
|
+
continue;
|
|
1407
|
+
}
|
|
1194
1408
|
okCount++;
|
|
1409
|
+
if (value.pending === true || value.direct === true) {
|
|
1410
|
+
pendingInstall.add(u.name);
|
|
1411
|
+
markDoneUpdate({ name: u.name, fromVersion: u.fromVersion, toVersion: u.toVersion });
|
|
1412
|
+
}
|
|
1195
1413
|
} catch (e) {
|
|
1196
1414
|
failures.push(`${u.name}\uFF1A${e instanceof Error ? e.message : String(e)}`);
|
|
1197
1415
|
}
|
package/dist/engine.d.ts
CHANGED
|
@@ -45,12 +45,17 @@ export declare class PluginCenterEngine extends Service {
|
|
|
45
45
|
private awesomeCache;
|
|
46
46
|
private awesomeDone;
|
|
47
47
|
private awesomeFetching;
|
|
48
|
+
/** 上次 fetch 失败时刻(0=未失败过):失败后冷却 60s 再重试,
|
|
49
|
+
* 防止瞬时网络故障让源永久失效(2026-08-22 dsh-market(0)实测)。 */
|
|
50
|
+
private awesomeFailedAt;
|
|
48
51
|
private ohMyDshCache;
|
|
49
52
|
private ohMyDshDone;
|
|
50
53
|
private ohMyDshFetching;
|
|
54
|
+
private ohMyDshFailedAt;
|
|
51
55
|
private dshMarketCache;
|
|
52
56
|
private dshMarketDone;
|
|
53
57
|
private dshMarketFetching;
|
|
58
|
+
private dshMarketFailedAt;
|
|
54
59
|
/** README-extracted screenshot URL per plugin name (lazy, P2). */
|
|
55
60
|
private readonly screenshotCache;
|
|
56
61
|
private installedNamesCache;
|
|
@@ -74,15 +79,15 @@ export declare class PluginCenterEngine extends Service {
|
|
|
74
79
|
dshVersion(): Promise<string>;
|
|
75
80
|
/** Non-group Loader entries, cross-matched with market categories. */
|
|
76
81
|
listInstalled(): Promise<InstalledPlugin[]>;
|
|
77
|
-
/** Start the awesome catalog fetch
|
|
82
|
+
/** Start the awesome catalog fetch (with failed-retry cooldown). */
|
|
78
83
|
private prefetchAwesome;
|
|
79
84
|
/** Backfill npm latest versions into the awesome cache (best-effort, concurrent). */
|
|
80
85
|
private fillNpmVersions;
|
|
81
86
|
/** Single-registry latest version for the market bulk backfill (npmmirror is fast). */
|
|
82
87
|
private fastNpmVersion;
|
|
83
|
-
/** Start the Oh-My-DSH fetch
|
|
88
|
+
/** Start the Oh-My-DSH fetch (single PLUGINS.md parse, with failed-retry cooldown). */
|
|
84
89
|
private prefetchOhMyDsh;
|
|
85
|
-
/** Start the dsh-market fetch
|
|
90
|
+
/** Start the dsh-market fetch (2BingLing/dsh-market, ~3900 plugins, trimmed, with failed-retry cooldown). */
|
|
86
91
|
private prefetchDshMarket;
|
|
87
92
|
/** Installed plugin names (no file IO) — cached so market polling stays cheap. */
|
|
88
93
|
private installedNames;
|
|
@@ -91,7 +96,14 @@ export declare class PluginCenterEngine extends Service {
|
|
|
91
96
|
/** Detect updates for every installed third-party/local plugin, TTL-cached. */
|
|
92
97
|
checkUpdates(sinceIso: string): Promise<UpdateDigest[]>;
|
|
93
98
|
install(spec: string): Promise<PnpmResult>;
|
|
94
|
-
/** Update one installed plugin to the detected target version (exact — see update.ts).
|
|
99
|
+
/** Update one installed plugin to the detected target version (exact — see update.ts).
|
|
100
|
+
* 三段式(2026-08-22):
|
|
101
|
+
* 1. 先尝试直装(绝大多数成功:纯 JS 包、或原生模块未被宿主加载——无锁);
|
|
102
|
+
* 2. 直装失败且是文件锁(EPERM/rename)→ 特殊路径:
|
|
103
|
+
* - SSiD(kernel 声明 SSID_PENDING_CONSUMER=1)→ 转两段式:预下载到
|
|
104
|
+
* ~/.ssid/pending-plugin-updates/,重启时由 kernel 在 boot DSH 前安装;
|
|
105
|
+
* - 官方 dsh web(无消费方)→ 仿社区市场返回可复制 CLI 指令;
|
|
106
|
+
* 3. 非锁失败(网络/版本)→ 原样报错。 */
|
|
95
107
|
update(name: string, version: string): Promise<PnpmResult>;
|
|
96
108
|
/** 串行执行一次 pnpm 操作并失效缓存(无论成败都放行链条后续任务)。 */
|
|
97
109
|
private enqueuePnpm;
|
package/dist/engine.js
CHANGED
|
@@ -7,12 +7,12 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import { Service } from '@deepseek-ai/cordis';
|
|
9
9
|
import { fileURLToPath } from 'node:url';
|
|
10
|
-
import { dirname, join } from 'node:path';
|
|
10
|
+
import { basename, dirname, join } from 'node:path';
|
|
11
11
|
import { mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
12
12
|
import { existsSync, readFileSync } from 'node:fs';
|
|
13
13
|
import { buildInstalledPlugin, clearPackageCache, resolvePackage } from "./meta.js";
|
|
14
14
|
import { fetchAwesomePluginsJson, fetchDshMarketPlugins, fetchOhMyDshOverrides, fetchOhMyDshPlugins, mapConcurrent, mergePlugins, } from "./market.js";
|
|
15
|
-
import { detectUpdate, installPlugin, updatePlugin } from "./update.js";
|
|
15
|
+
import { detectUpdate, installPlugin, preparePluginUpdate, updatePlugin } from "./update.js";
|
|
16
16
|
import { reconcileInstalled, readDependencyKeys } from "./reconcile.js";
|
|
17
17
|
import { readDisabledState, setDisabled } from "./toggle.js";
|
|
18
18
|
/** Runtime mirror of cordis FiberState (a cross-package const enum). */
|
|
@@ -63,12 +63,17 @@ export class PluginCenterEngine extends Service {
|
|
|
63
63
|
awesomeCache = [];
|
|
64
64
|
awesomeDone = false;
|
|
65
65
|
awesomeFetching = false;
|
|
66
|
+
/** 上次 fetch 失败时刻(0=未失败过):失败后冷却 60s 再重试,
|
|
67
|
+
* 防止瞬时网络故障让源永久失效(2026-08-22 dsh-market(0)实测)。 */
|
|
68
|
+
awesomeFailedAt = 0;
|
|
66
69
|
ohMyDshCache = [];
|
|
67
70
|
ohMyDshDone = false;
|
|
68
71
|
ohMyDshFetching = false;
|
|
72
|
+
ohMyDshFailedAt = 0;
|
|
69
73
|
dshMarketCache = [];
|
|
70
74
|
dshMarketDone = false;
|
|
71
75
|
dshMarketFetching = false;
|
|
76
|
+
dshMarketFailedAt = 0;
|
|
72
77
|
/** README-extracted screenshot URL per plugin name (lazy, P2). */
|
|
73
78
|
screenshotCache = new Map();
|
|
74
79
|
installedNamesCache = null;
|
|
@@ -156,11 +161,16 @@ export class PluginCenterEngine extends Service {
|
|
|
156
161
|
.map(p => ({ ...p, categories: categoryByName.get(p.name) ?? [] }))
|
|
157
162
|
.sort((a, b) => SOURCE_ORDER[a.source] - SOURCE_ORDER[b.source]);
|
|
158
163
|
}
|
|
159
|
-
/** Start the awesome catalog fetch
|
|
164
|
+
/** Start the awesome catalog fetch (with failed-retry cooldown). */
|
|
160
165
|
prefetchAwesome() {
|
|
161
|
-
if (this.awesomeFetching
|
|
166
|
+
if (this.awesomeFetching)
|
|
167
|
+
return;
|
|
168
|
+
if (this.awesomeDone && this.awesomeCache.length > 0)
|
|
169
|
+
return;
|
|
170
|
+
if (this.awesomeDone && Date.now() - this.awesomeFailedAt < 60_000)
|
|
162
171
|
return;
|
|
163
172
|
this.awesomeFetching = true;
|
|
173
|
+
this.awesomeDone = false;
|
|
164
174
|
void (async () => {
|
|
165
175
|
try {
|
|
166
176
|
const [plugins, overrides] = await Promise.all([fetchAwesomePluginsJson(), fetchOhMyDshOverrides()]);
|
|
@@ -173,7 +183,9 @@ export class PluginCenterEngine extends Service {
|
|
|
173
183
|
this.awesomeCache = merged;
|
|
174
184
|
await this.fillNpmVersions();
|
|
175
185
|
}
|
|
176
|
-
catch {
|
|
186
|
+
catch {
|
|
187
|
+
this.awesomeFailedAt = Date.now();
|
|
188
|
+
}
|
|
177
189
|
this.awesomeDone = true;
|
|
178
190
|
this.awesomeFetching = false;
|
|
179
191
|
})();
|
|
@@ -206,30 +218,44 @@ export class PluginCenterEngine extends Service {
|
|
|
206
218
|
return null;
|
|
207
219
|
}
|
|
208
220
|
}
|
|
209
|
-
/** Start the Oh-My-DSH fetch
|
|
221
|
+
/** Start the Oh-My-DSH fetch (single PLUGINS.md parse, with failed-retry cooldown). */
|
|
210
222
|
prefetchOhMyDsh() {
|
|
211
|
-
if (this.ohMyDshFetching
|
|
223
|
+
if (this.ohMyDshFetching)
|
|
224
|
+
return;
|
|
225
|
+
if (this.ohMyDshDone && this.ohMyDshCache.length > 0)
|
|
226
|
+
return;
|
|
227
|
+
if (this.ohMyDshDone && Date.now() - this.ohMyDshFailedAt < 60_000)
|
|
212
228
|
return;
|
|
213
229
|
this.ohMyDshFetching = true;
|
|
230
|
+
this.ohMyDshDone = false;
|
|
214
231
|
void (async () => {
|
|
215
232
|
try {
|
|
216
233
|
this.ohMyDshCache = mergePlugins([await fetchOhMyDshPlugins()]);
|
|
217
234
|
}
|
|
218
|
-
catch {
|
|
235
|
+
catch {
|
|
236
|
+
this.ohMyDshFailedAt = Date.now();
|
|
237
|
+
}
|
|
219
238
|
this.ohMyDshDone = true;
|
|
220
239
|
this.ohMyDshFetching = false;
|
|
221
240
|
})();
|
|
222
241
|
}
|
|
223
|
-
/** Start the dsh-market fetch
|
|
242
|
+
/** Start the dsh-market fetch (2BingLing/dsh-market, ~3900 plugins, trimmed, with failed-retry cooldown). */
|
|
224
243
|
prefetchDshMarket() {
|
|
225
|
-
if (this.dshMarketFetching
|
|
244
|
+
if (this.dshMarketFetching)
|
|
245
|
+
return;
|
|
246
|
+
if (this.dshMarketDone && this.dshMarketCache.length > 0)
|
|
247
|
+
return;
|
|
248
|
+
if (this.dshMarketDone && Date.now() - this.dshMarketFailedAt < 60_000)
|
|
226
249
|
return;
|
|
227
250
|
this.dshMarketFetching = true;
|
|
251
|
+
this.dshMarketDone = false;
|
|
228
252
|
void (async () => {
|
|
229
253
|
try {
|
|
230
254
|
this.dshMarketCache = mergePlugins([await fetchDshMarketPlugins()]);
|
|
231
255
|
}
|
|
232
|
-
catch {
|
|
256
|
+
catch {
|
|
257
|
+
this.dshMarketFailedAt = Date.now();
|
|
258
|
+
}
|
|
233
259
|
this.dshMarketDone = true;
|
|
234
260
|
this.dshMarketFetching = false;
|
|
235
261
|
})();
|
|
@@ -252,18 +278,18 @@ export class PluginCenterEngine extends Service {
|
|
|
252
278
|
const decorate = (plugins) => plugins.map(p => ({ ...p, installed: installedNames.has(p.name) || installedNames.has(p.spec) }));
|
|
253
279
|
if (source === 'awesome') {
|
|
254
280
|
this.prefetchAwesome();
|
|
255
|
-
return { plugins: decorate(this.awesomeCache), done: this.awesomeDone };
|
|
281
|
+
return { plugins: decorate(this.awesomeCache), done: this.awesomeDone && this.awesomeCache.length > 0 };
|
|
256
282
|
}
|
|
257
283
|
if (source === 'oh-my-dsh') {
|
|
258
284
|
this.prefetchOhMyDsh();
|
|
259
|
-
return { plugins: decorate(this.ohMyDshCache), done: this.ohMyDshDone };
|
|
285
|
+
return { plugins: decorate(this.ohMyDshCache), done: this.ohMyDshDone && this.ohMyDshCache.length > 0 };
|
|
260
286
|
}
|
|
261
287
|
if (source === 'dsh-market') {
|
|
262
288
|
this.prefetchDshMarket();
|
|
263
289
|
// Score-first order (best first) so the big catalog reads usefully.
|
|
264
290
|
return {
|
|
265
291
|
plugins: decorate(this.dshMarketCache).sort((a, b) => (b.score?.total ?? 0) - (a.score?.total ?? 0)),
|
|
266
|
-
done: this.dshMarketDone,
|
|
292
|
+
done: this.dshMarketDone && this.dshMarketCache.length > 0,
|
|
267
293
|
};
|
|
268
294
|
}
|
|
269
295
|
this.prefetchAwesome();
|
|
@@ -271,7 +297,9 @@ export class PluginCenterEngine extends Service {
|
|
|
271
297
|
this.prefetchDshMarket();
|
|
272
298
|
return {
|
|
273
299
|
plugins: decorate(mergePlugins([this.awesomeCache, this.ohMyDshCache, this.dshMarketCache])),
|
|
274
|
-
done: this.awesomeDone && this.
|
|
300
|
+
done: (this.awesomeDone && this.awesomeCache.length > 0)
|
|
301
|
+
&& (this.ohMyDshDone && this.ohMyDshCache.length > 0)
|
|
302
|
+
&& (this.dshMarketDone && this.dshMarketCache.length > 0),
|
|
275
303
|
};
|
|
276
304
|
}
|
|
277
305
|
/** Detect updates for every installed third-party/local plugin, TTL-cached. */
|
|
@@ -307,9 +335,27 @@ export class PluginCenterEngine extends Service {
|
|
|
307
335
|
return result;
|
|
308
336
|
});
|
|
309
337
|
}
|
|
310
|
-
/** Update one installed plugin to the detected target version (exact — see update.ts).
|
|
338
|
+
/** Update one installed plugin to the detected target version (exact — see update.ts).
|
|
339
|
+
* 三段式(2026-08-22):
|
|
340
|
+
* 1. 先尝试直装(绝大多数成功:纯 JS 包、或原生模块未被宿主加载——无锁);
|
|
341
|
+
* 2. 直装失败且是文件锁(EPERM/rename)→ 特殊路径:
|
|
342
|
+
* - SSiD(kernel 声明 SSID_PENDING_CONSUMER=1)→ 转两段式:预下载到
|
|
343
|
+
* ~/.ssid/pending-plugin-updates/,重启时由 kernel 在 boot DSH 前安装;
|
|
344
|
+
* - 官方 dsh web(无消费方)→ 仿社区市场返回可复制 CLI 指令;
|
|
345
|
+
* 3. 非锁失败(网络/版本)→ 原样报错。 */
|
|
311
346
|
async update(name, version) {
|
|
312
|
-
|
|
347
|
+
// 1) 直装:成败都在这一步见分晓(锁只发生在最后「替换已加载原生模块」)。
|
|
348
|
+
const direct = await this.enqueuePnpm(() => updatePlugin(name, version, this.baseUrl));
|
|
349
|
+
if (direct.ok)
|
|
350
|
+
return { ...direct, direct: true };
|
|
351
|
+
// 2) 锁类失败 → 特殊路径;其余失败原样返回。
|
|
352
|
+
if (!/EPERM|operation not permitted|EBUSY|rename/i.test(direct.detail))
|
|
353
|
+
return direct;
|
|
354
|
+
if (process.env.SSID_PENDING_CONSUMER === '1') {
|
|
355
|
+
return this.enqueuePnpm(() => preparePluginUpdate(name, version, this.baseUrl));
|
|
356
|
+
}
|
|
357
|
+
const profileName = basename(this.baseUrl) || 'web';
|
|
358
|
+
return { ok: true, detail: '', durationMs: direct.durationMs, command: `dsh plugin --profile ${profileName} add ${name}@${version}` };
|
|
313
359
|
}
|
|
314
360
|
/** 串行执行一次 pnpm 操作并失效缓存(无论成败都放行链条后续任务)。 */
|
|
315
361
|
enqueuePnpm(op) {
|
package/dist/market.js
CHANGED
|
@@ -108,9 +108,12 @@ function categorizeTags(tags) {
|
|
|
108
108
|
* file is ~10 MB, so the trimmed result (~1.5 MB) is what the engine caches.
|
|
109
109
|
*/
|
|
110
110
|
export async function fetchDshMarketPlugins() {
|
|
111
|
+
// 10MB 聚合文件 + raw.githubusercontent 不稳定:60s 曾实测超时失败
|
|
112
|
+
// (2026-08-22 用户看到 dsh-market(0)——失败后无重试,永远空),
|
|
113
|
+
// 超时放宽到 120s。
|
|
111
114
|
const res = await fetch('https://raw.githubusercontent.com/2BingLing/dsh-market/master/data/plugins.json', {
|
|
112
115
|
headers: UA,
|
|
113
|
-
signal: AbortSignal.timeout(
|
|
116
|
+
signal: AbortSignal.timeout(120000),
|
|
114
117
|
});
|
|
115
118
|
if (!res.ok)
|
|
116
119
|
throw new Error(`dsh-market plugins.json: HTTP ${res.status}`);
|
package/dist/rpc.js
CHANGED
|
@@ -43,7 +43,15 @@ export class PluginCenterRpc extends Service {
|
|
|
43
43
|
const result = await ctx.pluginCenter.update(name, version);
|
|
44
44
|
if (!result.ok)
|
|
45
45
|
return internal(`update ${name} 失败:${result.detail}`);
|
|
46
|
-
return {
|
|
46
|
+
return {
|
|
47
|
+
ok: true,
|
|
48
|
+
value: {
|
|
49
|
+
durationMs: result.durationMs,
|
|
50
|
+
direct: result.direct === true,
|
|
51
|
+
pending: result.pending === true,
|
|
52
|
+
command: result.command,
|
|
53
|
+
},
|
|
54
|
+
};
|
|
47
55
|
}
|
|
48
56
|
case 'toggle': {
|
|
49
57
|
const id = payload?.id;
|
package/dist/update.d.ts
CHANGED
|
@@ -23,7 +23,31 @@ export interface PnpmResult {
|
|
|
23
23
|
detail: string;
|
|
24
24
|
/** Wall-clock duration of the pnpm process (slow-but-successful is common). */
|
|
25
25
|
durationMs: number;
|
|
26
|
+
/** True when the update was installed DIRECTLY (files on disk now; a
|
|
27
|
+
* restart makes the running DSH pick them up), no locks were hit. */
|
|
28
|
+
direct?: boolean;
|
|
29
|
+
/** True when the update was only PREPARED (downloaded to the pending dir)
|
|
30
|
+
* and the real install is queued for the next SSiD/DSH startup. */
|
|
31
|
+
pending?: boolean;
|
|
32
|
+
/** Non-SSiD locked-update fallback: the CLI command the user should run
|
|
33
|
+
* after closing DSH (community-market style). */
|
|
34
|
+
command?: string;
|
|
26
35
|
}
|
|
36
|
+
/** One queued update: prepared tarball + install spec for the next boot. */
|
|
37
|
+
export interface PendingUpdate {
|
|
38
|
+
name: string;
|
|
39
|
+
version: string;
|
|
40
|
+
tgz: string;
|
|
41
|
+
at: number;
|
|
42
|
+
}
|
|
43
|
+
/** npm pack 生成的 tgz 文件名(scope 包的 @ 与 / 均转 -)。 */
|
|
44
|
+
export declare function tarballNameOf(name: string, version: string): string;
|
|
45
|
+
export declare function pendingUpdateDir(): string;
|
|
46
|
+
export declare function readPendingUpdates(): PendingUpdate[];
|
|
47
|
+
export declare function writePendingUpdates(entries: PendingUpdate[]): void;
|
|
48
|
+
/** Run `npm pack <spec>` into the pending dir and record the queued update.
|
|
49
|
+
* @returns the pnpm-style result with `pending: true` on success. */
|
|
50
|
+
export declare function preparePluginUpdate(name: string, version: string, profileDir: string): Promise<PnpmResult>;
|
|
27
51
|
/**
|
|
28
52
|
* Append one pnpm operation to `<profileDir>/plugin-center-pnpm.log`: time,
|
|
29
53
|
* command, cwd, exit, duration, and the captured output tail. Every install
|
package/dist/update.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* first (many community repos ship no release/tag/CHANGELOG — verified §7.2).
|
|
5
5
|
*/
|
|
6
6
|
import { spawn } from 'node:child_process';
|
|
7
|
-
import { appendFileSync, existsSync } from 'node:fs';
|
|
7
|
+
import { appendFileSync, existsSync, mkdirSync, readdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
8
8
|
import { homedir } from 'node:os';
|
|
9
9
|
import { join } from 'node:path';
|
|
10
10
|
import { compareVersions, satisfies } from "./semver.js";
|
|
@@ -70,6 +70,85 @@ export async function detectUpdate(name, localVersion, repoUrl, compatRange, loc
|
|
|
70
70
|
compatRange,
|
|
71
71
|
};
|
|
72
72
|
}
|
|
73
|
+
// ── 两段式更新(2026-08-22):预下载 + 重启时安装 ────────────────────────
|
|
74
|
+
// SSiD/DSH 运行中替换 node_modules 里的原生模块(node-pty 的 conpty.node
|
|
75
|
+
// 等)会被 Windows 锁死(EPERM:rename tmp -> 目标 失败)。与「禁用插件
|
|
76
|
+
// 写 patch + 重启生效」同款两段式:
|
|
77
|
+
// 1. 现在:npm pack 把 <name>@<version> 下载到 ~/.ssid/pending-plugin-updates/
|
|
78
|
+
// (不动 node_modules,无锁;网络/版本问题此刻暴露);
|
|
79
|
+
// 2. 重启(SSiD 启动、boot DSH 之前):shell/kernel.ts 消费清单,用
|
|
80
|
+
// `pnpm add -w <name>@<version>` 安装(无锁窗口、store 已缓存)。
|
|
81
|
+
// 注意:pnpm pack 只能打当前项目,远程 registry 包必须用 npm pack
|
|
82
|
+
// (2026-08-22 实测两者行为差异)。tgz 命名(npm pack 实测):
|
|
83
|
+
// 普通包 <name>-<version>.tgz;scope 包 <scope>-<name>-<version>.tgz。
|
|
84
|
+
const PENDING_DIR = join(homedir(), '.ssid', 'pending-plugin-updates');
|
|
85
|
+
const PENDING_INDEX = join(PENDING_DIR, 'index.json');
|
|
86
|
+
/** npm pack 生成的 tgz 文件名(scope 包的 @ 与 / 均转 -)。 */
|
|
87
|
+
export function tarballNameOf(name, version) {
|
|
88
|
+
return `${name.startsWith('@') ? name.slice(1).replace('/', '-') : name}-${version}.tgz`;
|
|
89
|
+
}
|
|
90
|
+
export function pendingUpdateDir() {
|
|
91
|
+
return PENDING_DIR;
|
|
92
|
+
}
|
|
93
|
+
export function readPendingUpdates() {
|
|
94
|
+
try {
|
|
95
|
+
const parsed = JSON.parse(readFileSync(PENDING_INDEX, 'utf8'));
|
|
96
|
+
if (!Array.isArray(parsed))
|
|
97
|
+
return [];
|
|
98
|
+
return parsed.filter((entry) => {
|
|
99
|
+
const e = entry;
|
|
100
|
+
return e !== null && typeof e.name === 'string' && typeof e.version === 'string' && typeof e.tgz === 'string';
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
catch {
|
|
104
|
+
return [];
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
export function writePendingUpdates(entries) {
|
|
108
|
+
mkdirSync(PENDING_DIR, { recursive: true });
|
|
109
|
+
writeFileSync(PENDING_INDEX, JSON.stringify(entries, null, 2) + '\n');
|
|
110
|
+
}
|
|
111
|
+
/** npm 候选命令:优先 PATH,回退用户级 npm 全局目录(同 pnpmCandidates 策略)。 */
|
|
112
|
+
function npmCandidates() {
|
|
113
|
+
const commands = ['npm', 'npm.cmd'];
|
|
114
|
+
const userNpm = join(homedir(), 'AppData', 'Roaming', 'npm');
|
|
115
|
+
for (const name of ['npm.cmd', 'npm.exe', 'npm']) {
|
|
116
|
+
const candidate = join(userNpm, name);
|
|
117
|
+
if (existsSync(candidate))
|
|
118
|
+
commands.push(candidate);
|
|
119
|
+
}
|
|
120
|
+
return commands;
|
|
121
|
+
}
|
|
122
|
+
/** Run `npm pack <spec>` into the pending dir and record the queued update.
|
|
123
|
+
* @returns the pnpm-style result with `pending: true` on success. */
|
|
124
|
+
export async function preparePluginUpdate(name, version, profileDir) {
|
|
125
|
+
const spec = `${name}@${version}`;
|
|
126
|
+
const tgz = join(PENDING_DIR, tarballNameOf(name, version));
|
|
127
|
+
// 清理同包其他版本的残留 tgz(保留同 name 最新一次准备)
|
|
128
|
+
try {
|
|
129
|
+
for (const file of readdirSync(PENDING_DIR)) {
|
|
130
|
+
if (file.endsWith('.tgz') && file !== tarballNameOf(name, version)) {
|
|
131
|
+
rmSync(join(PENDING_DIR, file), { force: true });
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
catch { /* pending dir absent is fine */ }
|
|
136
|
+
let last = { ok: false, detail: 'no npm candidate found', durationMs: 0 };
|
|
137
|
+
for (const command of npmCandidates()) {
|
|
138
|
+
last = await runOne(command, ['pack', spec, '--pack-destination', PENDING_DIR], profileDir);
|
|
139
|
+
if (last.ok)
|
|
140
|
+
break;
|
|
141
|
+
}
|
|
142
|
+
logPnpm(profileDir, ['npm', 'pack', spec, '--pack-destination', PENDING_DIR], last);
|
|
143
|
+
if (last.ok && existsSync(tgz)) {
|
|
144
|
+
const entries = readPendingUpdates().filter(entry => !(entry.name === name && entry.version === version));
|
|
145
|
+
entries.push({ name, version, tgz, at: Date.now() });
|
|
146
|
+
writePendingUpdates(entries);
|
|
147
|
+
return { ...last, pending: true };
|
|
148
|
+
}
|
|
149
|
+
// 失败:detail 里已带 npm 输出(网络/版本问题此时暴露,无需重启才发现)
|
|
150
|
+
return last;
|
|
151
|
+
}
|
|
73
152
|
/**
|
|
74
153
|
* Append one pnpm operation to `<profileDir>/plugin-center-pnpm.log`: time,
|
|
75
154
|
* command, cwd, exit, duration, and the captured output tail. Every install
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@max-null/dsh-plugin-center",
|
|
3
|
-
"version": "0.2.
|
|
4
|
-
"description": "Plugin center for DeepSeek Harness
|
|
3
|
+
"version": "0.2.4",
|
|
4
|
+
"description": "Plugin center for DeepSeek Harness 閳?installed metadata, community market, update detection, and What\u0027s New",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"publishConfig": {
|
|
7
7
|
"access": "public"
|