@max-null/dsh-plugin-center 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/client.js +88 -12
- package/dist/engine.d.ts +4 -0
- package/dist/engine.js +15 -8
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -22,9 +22,9 @@ Plugin center for [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-har
|
|
|
22
22
|
## Install / 安装
|
|
23
23
|
|
|
24
24
|
```sh
|
|
25
|
-
dsh plugin --profile web add github:Max-Null/dsh-plugin-center
|
|
26
|
-
# or, once published to npm / 或通过 npm(发布后)
|
|
27
25
|
dsh plugin --profile web add @max-null/dsh-plugin-center
|
|
26
|
+
# or from the GitHub source / 或从 GitHub 源码安装
|
|
27
|
+
dsh plugin --profile web add github:Max-Null/dsh-plugin-center
|
|
28
28
|
```
|
|
29
29
|
|
|
30
30
|
Restart `dsh web`, then open the plugin center from the header button (top-right) or the Settings → 插件中心 section.
|
package/client.js
CHANGED
|
@@ -370,7 +370,7 @@ function UpdatesView({ updates, refresh, updateOne, busy }) {
|
|
|
370
370
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "pc-spacer" }),
|
|
371
371
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: "pc-btn primary", disabled: busy !== null, onClick: () => {
|
|
372
372
|
updateOne(u.name);
|
|
373
|
-
}, children: busy === u.name ? "\u66F4\u65B0\u4E2D\u2026" : "\u66F4\u65B0" })
|
|
373
|
+
}, children: busy === u.name || busy === "__all__" ? "\u66F4\u65B0\u4E2D\u2026" : "\u66F4\u65B0" })
|
|
374
374
|
] }),
|
|
375
375
|
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)) })
|
|
376
376
|
] }, u.name)) });
|
|
@@ -389,12 +389,21 @@ function CenterPanel({ variant = "section" }) {
|
|
|
389
389
|
const [marketCount, setMarketCount] = (0, import_react.useState)(0);
|
|
390
390
|
const [updates, setUpdates] = (0, import_react.useState)(null);
|
|
391
391
|
const [busyUpdate, setBusyUpdate] = (0, import_react.useState)(null);
|
|
392
|
-
const
|
|
392
|
+
const [checking, setChecking] = (0, import_react.useState)(false);
|
|
393
|
+
const refreshUpdates = (0, import_react.useCallback)((silent = false) => {
|
|
394
|
+
setChecking(true);
|
|
393
395
|
void rpc("checkUpdates", { since: new Date(Date.now() - 30 * 864e5).toISOString() }).then(
|
|
394
396
|
(v) => {
|
|
395
|
-
|
|
397
|
+
const digests = v;
|
|
398
|
+
setUpdates(digests);
|
|
399
|
+
setChecking(false);
|
|
400
|
+
if (!silent) {
|
|
401
|
+
showToast(digests.length > 0 ? `\u53D1\u73B0 ${digests.length} \u4E2A\u53EF\u66F4\u65B0\u63D2\u4EF6` : "\u6240\u6709\u63D2\u4EF6\u5747\u4E3A\u6700\u65B0");
|
|
402
|
+
}
|
|
396
403
|
},
|
|
397
404
|
() => {
|
|
405
|
+
setChecking(false);
|
|
406
|
+
if (!silent) showToast("\u68C0\u67E5\u66F4\u65B0\u5931\u8D25\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5", "error");
|
|
398
407
|
}
|
|
399
408
|
);
|
|
400
409
|
}, []);
|
|
@@ -408,8 +417,33 @@ function CenterPanel({ variant = "section" }) {
|
|
|
408
417
|
() => {
|
|
409
418
|
}
|
|
410
419
|
);
|
|
411
|
-
refreshUpdates();
|
|
420
|
+
refreshUpdates(true);
|
|
412
421
|
}, [refreshUpdates]);
|
|
422
|
+
(0, import_react.useEffect)(() => {
|
|
423
|
+
let alive = true;
|
|
424
|
+
let timer = null;
|
|
425
|
+
const poll = async () => {
|
|
426
|
+
if (!alive) return;
|
|
427
|
+
try {
|
|
428
|
+
const r = await rpc("listMarket", { source: "all" });
|
|
429
|
+
if (!alive) return;
|
|
430
|
+
marketCache.all = { plugins: r.plugins, done: r.done };
|
|
431
|
+
setMarketCount(r.plugins.length);
|
|
432
|
+
if (!r.done) timer = setTimeout(() => {
|
|
433
|
+
void poll();
|
|
434
|
+
}, 5e3);
|
|
435
|
+
} catch {
|
|
436
|
+
if (alive) timer = setTimeout(() => {
|
|
437
|
+
void poll();
|
|
438
|
+
}, 15e3);
|
|
439
|
+
}
|
|
440
|
+
};
|
|
441
|
+
void poll();
|
|
442
|
+
return () => {
|
|
443
|
+
alive = false;
|
|
444
|
+
if (timer !== null) clearTimeout(timer);
|
|
445
|
+
};
|
|
446
|
+
}, []);
|
|
413
447
|
const updateOne = (name) => {
|
|
414
448
|
setBusyUpdate(name);
|
|
415
449
|
void rpc("update", { name }).then(
|
|
@@ -423,12 +457,26 @@ function CenterPanel({ variant = "section" }) {
|
|
|
423
457
|
}
|
|
424
458
|
);
|
|
425
459
|
};
|
|
426
|
-
const updateAll = () => {
|
|
427
|
-
if (updates
|
|
428
|
-
|
|
429
|
-
|
|
460
|
+
const updateAll = async () => {
|
|
461
|
+
if (updates === null || updates.length === 0) return;
|
|
462
|
+
setBusyUpdate("__all__");
|
|
463
|
+
let okCount = 0;
|
|
464
|
+
const failures = [];
|
|
465
|
+
for (const u of updates) {
|
|
466
|
+
try {
|
|
467
|
+
await rpc("update", { name: u.name });
|
|
468
|
+
okCount++;
|
|
469
|
+
} catch (e) {
|
|
470
|
+
failures.push(`${u.name}\uFF1A${e instanceof Error ? e.message : String(e)}`);
|
|
430
471
|
}
|
|
431
472
|
}
|
|
473
|
+
setBusyUpdate(null);
|
|
474
|
+
if (failures.length === 0) {
|
|
475
|
+
showToast(`\u5DF2\u66F4\u65B0 ${okCount} \u4E2A\u63D2\u4EF6\uFF0C\u91CD\u542F dsh web \u540E\u751F\u6548`);
|
|
476
|
+
} else {
|
|
477
|
+
showToast(`\u66F4\u65B0\u5B8C\u6210\uFF1A\u6210\u529F ${okCount}\uFF0C\u5931\u8D25 ${failures.length}\uFF08${failures.join("\uFF1B")}\uFF09`, "error");
|
|
478
|
+
}
|
|
479
|
+
refreshUpdates();
|
|
432
480
|
};
|
|
433
481
|
const tab = (v, label, count) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", { type: "button", className: `pc-tab${view === v ? " active" : ""}`, onClick: () => {
|
|
434
482
|
setView(v);
|
|
@@ -452,9 +500,13 @@ function CenterPanel({ variant = "section" }) {
|
|
|
452
500
|
failedCount
|
|
453
501
|
] }),
|
|
454
502
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "pc-spacer" }),
|
|
455
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: "pc-btn",
|
|
456
|
-
|
|
457
|
-
|
|
503
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: "pc-btn", disabled: checking, onClick: () => {
|
|
504
|
+
refreshUpdates();
|
|
505
|
+
}, children: checking ? "\u68C0\u67E5\u4E2D\u2026" : "\u68C0\u67E5\u66F4\u65B0" }),
|
|
506
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", { className: "pc-btn primary", disabled: !updates?.length || busyUpdate !== null, onClick: () => {
|
|
507
|
+
void updateAll();
|
|
508
|
+
}, children: [
|
|
509
|
+
"\u66F4\u65B0\u5168\u90E8\uFF08",
|
|
458
510
|
updates?.length ?? 0,
|
|
459
511
|
"\uFF09"
|
|
460
512
|
] })
|
|
@@ -563,7 +615,28 @@ function Toast() {
|
|
|
563
615
|
}
|
|
564
616
|
function WhatsNewDialog() {
|
|
565
617
|
const open = useWhatsNewOpen();
|
|
618
|
+
const [busy, setBusy] = (0, import_react.useState)(false);
|
|
566
619
|
if (!open || whatsNewDigests.length === 0) return null;
|
|
620
|
+
const updateNow = async () => {
|
|
621
|
+
setBusy(true);
|
|
622
|
+
let okCount = 0;
|
|
623
|
+
const failures = [];
|
|
624
|
+
for (const u of whatsNewDigests) {
|
|
625
|
+
try {
|
|
626
|
+
await rpc("update", { name: u.name });
|
|
627
|
+
okCount++;
|
|
628
|
+
} catch (e) {
|
|
629
|
+
failures.push(`${u.name}\uFF1A${e instanceof Error ? e.message : String(e)}`);
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
setBusy(false);
|
|
633
|
+
if (failures.length === 0) {
|
|
634
|
+
showToast(`\u5DF2\u66F4\u65B0 ${okCount} \u4E2A\u63D2\u4EF6\uFF0C\u91CD\u542F dsh web \u540E\u751F\u6548`);
|
|
635
|
+
closeWhatsNew();
|
|
636
|
+
} else {
|
|
637
|
+
showToast(`\u66F4\u65B0\u5B8C\u6210\uFF1A\u6210\u529F ${okCount}\uFF0C\u5931\u8D25 ${failures.length}\uFF08${failures.join("\uFF1B")}\uFF09`, "error");
|
|
638
|
+
}
|
|
639
|
+
};
|
|
567
640
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "pc-overlay", role: "presentation", children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "pc-panel", style: { width: "540px" }, role: "dialog", "aria-modal": "true", "aria-label": "\u63D2\u4EF6\u66F4\u65B0", children: [
|
|
568
641
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "pc-panel-head", children: [
|
|
569
642
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "pc-title", children: "\u63D2\u4EF6\u66F4\u65B0" }),
|
|
@@ -586,7 +659,10 @@ function WhatsNewDialog() {
|
|
|
586
659
|
] }, u.name)),
|
|
587
660
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { display: "flex", justifyContent: "flex-end", gap: "8px", marginTop: "16px" }, children: [
|
|
588
661
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: "pc-btn", onClick: closeWhatsNew, children: "\u7A0D\u540E" }),
|
|
589
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: "pc-btn
|
|
662
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: "pc-btn", onClick: closeWhatsNew, children: "\u5168\u90E8\u6807\u8BB0\u5DF2\u8BFB" }),
|
|
663
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: "pc-btn primary", disabled: busy, onClick: () => {
|
|
664
|
+
void updateNow();
|
|
665
|
+
}, children: busy ? "\u66F4\u65B0\u4E2D\u2026" : "\u7ACB\u5373\u66F4\u65B0" })
|
|
590
666
|
] })
|
|
591
667
|
] })
|
|
592
668
|
] }) });
|
package/dist/engine.d.ts
CHANGED
|
@@ -33,6 +33,8 @@ export declare class PluginCenterEngine extends Service {
|
|
|
33
33
|
private installedNamesCache;
|
|
34
34
|
private updatesCache;
|
|
35
35
|
private readonly updatesTtlMs;
|
|
36
|
+
/** 串行链:同 profile 的 pnpm 调用禁止并发(并发 add 会撞 store 锁/写坏 lock)。 */
|
|
37
|
+
private pnpmChain;
|
|
36
38
|
constructor(ctx: Context);
|
|
37
39
|
/** Background preload of market + installed metadata; failures fall back to lazy load. */
|
|
38
40
|
private warmup;
|
|
@@ -65,6 +67,8 @@ export declare class PluginCenterEngine extends Service {
|
|
|
65
67
|
checkUpdates(sinceIso: string): Promise<UpdateDigest[]>;
|
|
66
68
|
install(spec: string): Promise<boolean>;
|
|
67
69
|
update(name: string): Promise<boolean>;
|
|
70
|
+
/** 串行执行一次 pnpm 操作并失效缓存(无论成败都放行链条后续任务)。 */
|
|
71
|
+
private enqueuePnpm;
|
|
68
72
|
/** Temporary diagnostics for the empty-update bug; removed once root-caused. */
|
|
69
73
|
debug(): Promise<{
|
|
70
74
|
baseUrl: string;
|
package/dist/engine.js
CHANGED
|
@@ -34,6 +34,8 @@ export class PluginCenterEngine extends Service {
|
|
|
34
34
|
installedNamesCache = null;
|
|
35
35
|
updatesCache = null;
|
|
36
36
|
updatesTtlMs = 5 * 60_000;
|
|
37
|
+
/** 串行链:同 profile 的 pnpm 调用禁止并发(并发 add 会撞 store 锁/写坏 lock)。 */
|
|
38
|
+
pnpmChain = Promise.resolve();
|
|
37
39
|
constructor(ctx) {
|
|
38
40
|
super(ctx, 'pluginCenter');
|
|
39
41
|
// Warm caches in the background once the host is up, so opening the panel
|
|
@@ -221,16 +223,21 @@ export class PluginCenterEngine extends Service {
|
|
|
221
223
|
return this.updatesCache.digests;
|
|
222
224
|
}
|
|
223
225
|
async install(spec) {
|
|
224
|
-
|
|
225
|
-
this.installedNamesCache = null;
|
|
226
|
-
this.updatesCache = null;
|
|
227
|
-
return ok;
|
|
226
|
+
return this.enqueuePnpm(() => installPlugin(spec, this.baseUrl));
|
|
228
227
|
}
|
|
229
228
|
async update(name) {
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
229
|
+
return this.enqueuePnpm(() => updatePlugin(name, this.baseUrl));
|
|
230
|
+
}
|
|
231
|
+
/** 串行执行一次 pnpm 操作并失效缓存(无论成败都放行链条后续任务)。 */
|
|
232
|
+
enqueuePnpm(op) {
|
|
233
|
+
const run = this.pnpmChain.then(async () => {
|
|
234
|
+
const ok = await op();
|
|
235
|
+
this.installedNamesCache = null;
|
|
236
|
+
this.updatesCache = null;
|
|
237
|
+
return ok;
|
|
238
|
+
});
|
|
239
|
+
this.pnpmChain = run.catch(() => { });
|
|
240
|
+
return run;
|
|
234
241
|
}
|
|
235
242
|
/** Temporary diagnostics for the empty-update bug; removed once root-caused. */
|
|
236
243
|
async debug() {
|
package/package.json
CHANGED