@max-null/dsh-plugin-center 0.1.0 → 0.1.2
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 +292 -87
- package/dist/engine.d.ts +12 -2
- package/dist/engine.js +15 -8
- package/dist/rpc.js +8 -2
- package/dist/update.d.ts +15 -7
- package/dist/update.js +39 -16
- 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
|
@@ -203,9 +203,143 @@ async function checkWhatNew() {
|
|
|
203
203
|
} catch {
|
|
204
204
|
}
|
|
205
205
|
}
|
|
206
|
-
var SOURCE_LABEL = { official: "\u5B98\u65B9", installed: "\u7528\u6237\u5B89\u88C5", local: "\u672C\u5730\u5F00\u53D1", builtin: "\u5185\u7F6E" };
|
|
207
206
|
var CATEGORIES = ["ui", "usage", "theme", "model", "session", "memory", "tools", "vision", "skill", "workflow", "notify", "dev", "market", "fun"];
|
|
207
|
+
var STRINGS = {
|
|
208
|
+
zh: {
|
|
209
|
+
title: "\u63D2\u4EF6\u4E2D\u5FC3",
|
|
210
|
+
tabInstalled: "\u5DF2\u5B89\u88C5",
|
|
211
|
+
tabMarket: "\u5E02\u573A",
|
|
212
|
+
tabUpdates: "\u66F4\u65B0",
|
|
213
|
+
headSummary: "\u5DF2\u5B89\u88C5 {a} \xB7 \u6709\u66F4\u65B0 {b} \xB7 \u5931\u6548 {c}",
|
|
214
|
+
check: "\u68C0\u67E5\u66F4\u65B0",
|
|
215
|
+
checking: "\u68C0\u67E5\u4E2D\u2026",
|
|
216
|
+
updateAll: "\u66F4\u65B0\u5168\u90E8\uFF08{n}\uFF09",
|
|
217
|
+
searchInstalled: "\u641C\u7D22\u5DF2\u5B89\u88C5\u63D2\u4EF6",
|
|
218
|
+
allSources: "\u5168\u90E8\u6765\u6E90",
|
|
219
|
+
srcOfficial: "\u5B98\u65B9",
|
|
220
|
+
srcInstalled: "\u7528\u6237\u5B89\u88C5",
|
|
221
|
+
srcLocal: "\u672C\u5730\u5F00\u53D1",
|
|
222
|
+
srcBuiltin: "\u5185\u7F6E",
|
|
223
|
+
all: "\u5168\u90E8",
|
|
224
|
+
searchMarket: "\u641C\u7D22\u793E\u533A\u63D2\u4EF6",
|
|
225
|
+
allMarkets: "\u5168\u90E8\u6E90",
|
|
226
|
+
gridDouble: "\u53CC\u5217\u7F51\u683C",
|
|
227
|
+
gridSingle: "\u5355\u5217\u5217\u8868",
|
|
228
|
+
loadFailed: "\u52A0\u8F7D\u5931\u8D25\uFF1A{e}",
|
|
229
|
+
loading: "\u52A0\u8F7D\u4E2D\u2026",
|
|
230
|
+
marketLoading: "\u52A0\u8F7D\u5E02\u573A\u76EE\u5F55\u4E2D\u2026",
|
|
231
|
+
loadMore: "\u6B63\u5728\u52A0\u8F7D\u66F4\u591A\u2026\uFF08\u5DF2\u52A0\u8F7D {n} \u4E2A\uFF09",
|
|
232
|
+
checkingUpdates: "\u68C0\u67E5\u66F4\u65B0\u4E2D\u2026",
|
|
233
|
+
noUpdates: "\u6CA1\u6709\u53EF\u7528\u7684\u66F4\u65B0\u3002",
|
|
234
|
+
recheck: "\u91CD\u65B0\u68C0\u67E5",
|
|
235
|
+
incompat: "\u4E0D\u517C\u5BB9\u5F53\u524D DSH",
|
|
236
|
+
update: "\u66F4\u65B0",
|
|
237
|
+
updating: "\u66F4\u65B0\u4E2D\u2026",
|
|
238
|
+
install: "\u5B89\u88C5",
|
|
239
|
+
installing: "\u5B89\u88C5\u4E2D\u2026",
|
|
240
|
+
pendingRestart: "\u5F85\u91CD\u542F\u751F\u6548",
|
|
241
|
+
installedTag: "\u5DF2\u5B89\u88C5",
|
|
242
|
+
disabledTag: "\u5DF2\u7981\u7528",
|
|
243
|
+
requiresDsh: "\u8981\u6C42 DSH {r}",
|
|
244
|
+
installQueued: "\u5DF2\u53D1\u8D77\u5B89\u88C5 {n}\uFF0C\u91CD\u542F dsh web \u540E\u751F\u6548",
|
|
245
|
+
installFailed: "\u5B89\u88C5\u5931\u8D25\uFF1A{e}",
|
|
246
|
+
installNotApplied: "\u5B89\u88C5\u672A\u751F\u6548",
|
|
247
|
+
updatedOne: "\u5DF2\u66F4\u65B0 {n}\uFF0C\u91CD\u542F dsh web \u540E\u751F\u6548",
|
|
248
|
+
updateFailed: "\u66F4\u65B0\u5931\u8D25\uFF1A{e}",
|
|
249
|
+
updateNotApplied: "\u66F4\u65B0\u672A\u751F\u6548",
|
|
250
|
+
updatedMany: "\u5DF2\u66F4\u65B0 {n} \u4E2A\u63D2\u4EF6\uFF0C\u91CD\u542F dsh web \u540E\u751F\u6548",
|
|
251
|
+
updateSummary: "\u66F4\u65B0\u5B8C\u6210\uFF1A\u6210\u529F {a}\uFF0C\u5931\u8D25 {b}\uFF08{c}\uFF09",
|
|
252
|
+
whatsNewTitle: "\u63D2\u4EF6\u66F4\u65B0",
|
|
253
|
+
whatsNewSub: "{n} \u4E2A\u63D2\u4EF6\u6709\u65B0\u7248\u672C",
|
|
254
|
+
later: "\u7A0D\u540E",
|
|
255
|
+
markAllRead: "\u5168\u90E8\u6807\u8BB0\u5DF2\u8BFB",
|
|
256
|
+
updateNow: "\u7ACB\u5373\u66F4\u65B0",
|
|
257
|
+
close: "\u5173\u95ED",
|
|
258
|
+
checkFail: "\u68C0\u67E5\u66F4\u65B0\u5931\u8D25\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5",
|
|
259
|
+
foundUpdates: "\u53D1\u73B0 {n} \u4E2A\u53EF\u66F4\u65B0\u63D2\u4EF6",
|
|
260
|
+
allUpToDate: "\u6240\u6709\u63D2\u4EF6\u5747\u4E3A\u6700\u65B0"
|
|
261
|
+
},
|
|
262
|
+
en: {
|
|
263
|
+
title: "Plugin Center",
|
|
264
|
+
tabInstalled: "Installed",
|
|
265
|
+
tabMarket: "Market",
|
|
266
|
+
tabUpdates: "Updates",
|
|
267
|
+
headSummary: "{a} installed \xB7 {b} updates \xB7 {c} failed",
|
|
268
|
+
check: "Check updates",
|
|
269
|
+
checking: "Checking\u2026",
|
|
270
|
+
updateAll: "Update all\uFF08{n}\uFF09",
|
|
271
|
+
searchInstalled: "Search installed plugins",
|
|
272
|
+
allSources: "All sources",
|
|
273
|
+
srcOfficial: "Official",
|
|
274
|
+
srcInstalled: "User installed",
|
|
275
|
+
srcLocal: "Local dev",
|
|
276
|
+
srcBuiltin: "Built-in",
|
|
277
|
+
all: "All",
|
|
278
|
+
searchMarket: "Search community plugins",
|
|
279
|
+
allMarkets: "All sources",
|
|
280
|
+
gridDouble: "Two-column grid",
|
|
281
|
+
gridSingle: "Single-column list",
|
|
282
|
+
loadFailed: "Failed to load: {e}",
|
|
283
|
+
loading: "Loading\u2026",
|
|
284
|
+
marketLoading: "Loading market catalog\u2026",
|
|
285
|
+
loadMore: "Loading more\u2026 ({n} loaded)",
|
|
286
|
+
checkingUpdates: "Checking for updates\u2026",
|
|
287
|
+
noUpdates: "No updates available.",
|
|
288
|
+
recheck: "Check again",
|
|
289
|
+
incompat: "Incompatible with current DSH",
|
|
290
|
+
update: "Update",
|
|
291
|
+
updating: "Updating\u2026",
|
|
292
|
+
install: "Install",
|
|
293
|
+
installing: "Installing\u2026",
|
|
294
|
+
pendingRestart: "Restart pending",
|
|
295
|
+
installedTag: "Installed",
|
|
296
|
+
disabledTag: "Disabled",
|
|
297
|
+
requiresDsh: "Requires DSH {r}",
|
|
298
|
+
installQueued: "Install of {n} started; restart dsh web to take effect",
|
|
299
|
+
installFailed: "Install failed: {e}",
|
|
300
|
+
installNotApplied: "Install did not take effect",
|
|
301
|
+
updatedOne: "Updated {n}; restart dsh web to take effect",
|
|
302
|
+
updateFailed: "Update failed: {e}",
|
|
303
|
+
updateNotApplied: "Update did not take effect",
|
|
304
|
+
updatedMany: "Updated {n} plugins; restart dsh web to take effect",
|
|
305
|
+
updateSummary: "Update done: {a} succeeded, {b} failed ({c})",
|
|
306
|
+
whatsNewTitle: "Plugin updates",
|
|
307
|
+
whatsNewSub: "{n} plugins have new versions",
|
|
308
|
+
later: "Later",
|
|
309
|
+
markAllRead: "Mark all read",
|
|
310
|
+
updateNow: "Update now",
|
|
311
|
+
close: "Close",
|
|
312
|
+
checkFail: "Failed to check updates, please retry later",
|
|
313
|
+
foundUpdates: "{n} updates found",
|
|
314
|
+
allUpToDate: "All plugins are up to date"
|
|
315
|
+
}
|
|
316
|
+
};
|
|
317
|
+
var localeId = "zh";
|
|
318
|
+
var localeListeners = /* @__PURE__ */ new Set();
|
|
319
|
+
function adoptLocale(id) {
|
|
320
|
+
const next = id === "en" ? "en" : "zh";
|
|
321
|
+
if (next === localeId) return;
|
|
322
|
+
localeId = next;
|
|
323
|
+
localeListeners.forEach((l) => l());
|
|
324
|
+
}
|
|
325
|
+
function fmt(tpl, vars = {}) {
|
|
326
|
+
return tpl.replace(/\{(\w+)\}/g, (_, k) => String(vars[k] ?? ""));
|
|
327
|
+
}
|
|
328
|
+
function useT() {
|
|
329
|
+
const [id, setId] = (0, import_react.useState)(localeId);
|
|
330
|
+
(0, import_react.useEffect)(() => {
|
|
331
|
+
const l = () => {
|
|
332
|
+
setId(localeId);
|
|
333
|
+
};
|
|
334
|
+
localeListeners.add(l);
|
|
335
|
+
return () => {
|
|
336
|
+
localeListeners.delete(l);
|
|
337
|
+
};
|
|
338
|
+
}, []);
|
|
339
|
+
return (key, vars) => fmt(STRINGS[id][key] ?? STRINGS.zh[key], vars);
|
|
340
|
+
}
|
|
208
341
|
function InstalledView({ search, category, source }) {
|
|
342
|
+
const t = useT();
|
|
209
343
|
const [items, setItems] = (0, import_react.useState)(installedCache);
|
|
210
344
|
const [error, setError] = (0, import_react.useState)(null);
|
|
211
345
|
(0, import_react.useEffect)(() => {
|
|
@@ -224,11 +358,14 @@ function InstalledView({ search, category, source }) {
|
|
|
224
358
|
alive = false;
|
|
225
359
|
};
|
|
226
360
|
}, []);
|
|
227
|
-
if (error !== null) return /* @__PURE__ */ (0, import_jsx_runtime.
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
361
|
+
if (error !== null) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "pc-sub", children: t("loadFailed", { e: error }) });
|
|
362
|
+
if (items === null) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "pc-sub", children: t("loading") });
|
|
363
|
+
const srcLabel = {
|
|
364
|
+
official: t("srcOfficial"),
|
|
365
|
+
installed: t("srcInstalled"),
|
|
366
|
+
local: t("srcLocal"),
|
|
367
|
+
builtin: t("srcBuiltin")
|
|
368
|
+
};
|
|
232
369
|
const q = search.trim().toLowerCase();
|
|
233
370
|
const filtered = items.filter((p) => {
|
|
234
371
|
const matchSearch = q === "" || p.displayName.toLowerCase().includes(q) || (p.description ?? "").toLowerCase().includes(q) || p.name.toLowerCase().includes(q);
|
|
@@ -243,22 +380,20 @@ function InstalledView({ search, category, source }) {
|
|
|
243
380
|
"v",
|
|
244
381
|
p.version
|
|
245
382
|
] }),
|
|
246
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: `pc-badge ${p.source}`, children:
|
|
383
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: `pc-badge ${p.source}`, children: srcLabel[p.source] }),
|
|
247
384
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "pc-spacer" }),
|
|
248
385
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: `pc-dot${p.fiberPhase === "failed" ? " failed" : ""}` })
|
|
249
386
|
] }),
|
|
250
387
|
p.description !== null && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "pc-desc", children: p.description }),
|
|
251
388
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "pc-meta", children: [
|
|
252
389
|
p.categories.map((c) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "pc-tag", children: c }, c)),
|
|
253
|
-
p.compatRange !== null && /* @__PURE__ */ (0, import_jsx_runtime.
|
|
254
|
-
|
|
255
|
-
p.compatRange
|
|
256
|
-
] }),
|
|
257
|
-
!p.enabled && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "pc-tag", children: "\u5DF2\u7981\u7528" })
|
|
390
|
+
p.compatRange !== null && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "pc-tag", children: t("requiresDsh", { r: p.compatRange }) }),
|
|
391
|
+
!p.enabled && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "pc-tag", children: t("disabledTag") })
|
|
258
392
|
] })
|
|
259
393
|
] }, p.entryId)) });
|
|
260
394
|
}
|
|
261
395
|
function MarketView({ category, single, source, search, onCount }) {
|
|
396
|
+
const t = useT();
|
|
262
397
|
const [items, setItems] = (0, import_react.useState)(marketCache[source]?.plugins ?? []);
|
|
263
398
|
const [done, setDone] = (0, import_react.useState)(marketCache[source]?.done ?? false);
|
|
264
399
|
const [error, setError] = (0, import_react.useState)(null);
|
|
@@ -296,20 +431,19 @@ function MarketView({ category, single, source, search, onCount }) {
|
|
|
296
431
|
if (timer !== void 0) clearTimeout(timer);
|
|
297
432
|
};
|
|
298
433
|
}, [source, onCount]);
|
|
299
|
-
if (error !== null) return /* @__PURE__ */ (0, import_jsx_runtime.
|
|
300
|
-
|
|
301
|
-
error
|
|
302
|
-
] });
|
|
303
|
-
if (items.length === 0 && !done) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "pc-sub", children: "\u52A0\u8F7D\u5E02\u573A\u76EE\u5F55\u4E2D\u2026" });
|
|
434
|
+
if (error !== null) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "pc-sub", children: t("loadFailed", { e: error }) });
|
|
435
|
+
if (items.length === 0 && !done) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "pc-sub", children: t("marketLoading") });
|
|
304
436
|
const q = search.trim().toLowerCase();
|
|
305
437
|
const filtered = items.filter((m) => {
|
|
306
438
|
const matchSearch = q === "" || m.name.toLowerCase().includes(q) || (m.description.zh || m.description.en).toLowerCase().includes(q);
|
|
307
439
|
return matchSearch && (category === null || m.categories.includes(category));
|
|
308
440
|
});
|
|
441
|
+
const descOf = (m) => localeId === "en" ? m.description.en || m.description.zh : m.description.zh || m.description.en;
|
|
309
442
|
const install = (m) => {
|
|
310
443
|
setBusy(m.name);
|
|
311
444
|
void rpc("install", { spec: m.spec }).then(
|
|
312
|
-
() => {
|
|
445
|
+
(v) => {
|
|
446
|
+
if (v !== true) throw new Error(t("installNotApplied"));
|
|
313
447
|
setBusy(null);
|
|
314
448
|
pendingInstall.add(m.spec);
|
|
315
449
|
for (const key of Object.keys(marketCache)) {
|
|
@@ -317,11 +451,11 @@ function MarketView({ category, single, source, search, onCount }) {
|
|
|
317
451
|
if (c !== void 0) c.plugins = c.plugins.map((p) => p.name === m.name ? { ...p, installed: true } : p);
|
|
318
452
|
}
|
|
319
453
|
setItems((prev) => prev.map((p) => p.name === m.name ? { ...p, installed: true } : p));
|
|
320
|
-
showToast(
|
|
454
|
+
showToast(t("installQueued", { n: m.name }));
|
|
321
455
|
},
|
|
322
456
|
(e) => {
|
|
323
457
|
setBusy(null);
|
|
324
|
-
showToast(
|
|
458
|
+
showToast(t("installFailed", { e: e instanceof Error ? e.message : String(e) }), "error");
|
|
325
459
|
}
|
|
326
460
|
);
|
|
327
461
|
};
|
|
@@ -338,27 +472,24 @@ function MarketView({ category, single, source, search, onCount }) {
|
|
|
338
472
|
m.version
|
|
339
473
|
] })
|
|
340
474
|
] }),
|
|
341
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "pc-desc", children: m
|
|
475
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "pc-desc", children: descOf(m) }),
|
|
342
476
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "pc-meta", children: [
|
|
343
477
|
m.categories.map((c) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "pc-tag", children: c }, c)),
|
|
344
478
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "pc-spacer" }),
|
|
345
|
-
m.installed || pendingInstall.has(m.spec) ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "pc-tag", children: pendingInstall.has(m.spec) ? "
|
|
479
|
+
m.installed || pendingInstall.has(m.spec) ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "pc-tag", children: pendingInstall.has(m.spec) ? t("pendingRestart") : t("installedTag") }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: "pc-btn primary", disabled: busy !== null, onClick: () => {
|
|
346
480
|
install(m);
|
|
347
|
-
}, children: busy === m.name ? "
|
|
481
|
+
}, children: busy === m.name ? t("installing") : t("install") })
|
|
348
482
|
] })
|
|
349
483
|
] }, m.name)) }),
|
|
350
|
-
!done && /* @__PURE__ */ (0, import_jsx_runtime.
|
|
351
|
-
"\u6B63\u5728\u52A0\u8F7D\u66F4\u591A\u2026\uFF08\u5DF2\u52A0\u8F7D ",
|
|
352
|
-
items.length,
|
|
353
|
-
" \u4E2A\uFF09"
|
|
354
|
-
] })
|
|
484
|
+
!done && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "pc-sub", children: t("loadMore", { n: items.length }) })
|
|
355
485
|
] });
|
|
356
486
|
}
|
|
357
487
|
function UpdatesView({ updates, refresh, updateOne, busy }) {
|
|
358
|
-
|
|
488
|
+
const t = useT();
|
|
489
|
+
if (updates === null) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "pc-sub", children: t("checkingUpdates") });
|
|
359
490
|
if (updates.length === 0) return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
|
|
360
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "pc-sub", children: "
|
|
361
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: "pc-btn", onClick: refresh, children: "
|
|
491
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "pc-sub", children: t("noUpdates") }),
|
|
492
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: "pc-btn", onClick: refresh, children: t("recheck") })
|
|
362
493
|
] });
|
|
363
494
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { children: updates.map((u) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "pc-card", children: [
|
|
364
495
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "pc-row", children: [
|
|
@@ -366,16 +497,17 @@ function UpdatesView({ updates, refresh, updateOne, busy }) {
|
|
|
366
497
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "pc-ver", children: u.fromVersion }),
|
|
367
498
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "pc-ver", children: "\u2192" }),
|
|
368
499
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { color: "var(--dsw-alias-state-business-primary)", fontWeight: 500 }, children: u.toVersion }),
|
|
369
|
-
u.compat === "incompatible" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "pc-tag danger", children: "
|
|
500
|
+
u.compat === "incompatible" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "pc-tag danger", children: t("incompat") }),
|
|
370
501
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "pc-spacer" }),
|
|
371
502
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: "pc-btn primary", disabled: busy !== null, onClick: () => {
|
|
372
503
|
updateOne(u.name);
|
|
373
|
-
}, children: busy === u.name ? "
|
|
504
|
+
}, children: busy === u.name || busy === "__all__" ? t("updating") : t("update") })
|
|
374
505
|
] }),
|
|
375
506
|
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
507
|
] }, u.name)) });
|
|
377
508
|
}
|
|
378
509
|
function CenterPanel({ variant = "section" }) {
|
|
510
|
+
const t = useT();
|
|
379
511
|
const [view, setView] = (0, import_react.useState)("installed");
|
|
380
512
|
const [category, setCategory] = (0, import_react.useState)(null);
|
|
381
513
|
const [single, setSingle] = (0, import_react.useState)(false);
|
|
@@ -389,15 +521,24 @@ function CenterPanel({ variant = "section" }) {
|
|
|
389
521
|
const [marketCount, setMarketCount] = (0, import_react.useState)(0);
|
|
390
522
|
const [updates, setUpdates] = (0, import_react.useState)(null);
|
|
391
523
|
const [busyUpdate, setBusyUpdate] = (0, import_react.useState)(null);
|
|
392
|
-
const
|
|
524
|
+
const [checking, setChecking] = (0, import_react.useState)(false);
|
|
525
|
+
const refreshUpdates = (0, import_react.useCallback)((silent = false) => {
|
|
526
|
+
setChecking(true);
|
|
393
527
|
void rpc("checkUpdates", { since: new Date(Date.now() - 30 * 864e5).toISOString() }).then(
|
|
394
528
|
(v) => {
|
|
395
|
-
|
|
529
|
+
const digests = v;
|
|
530
|
+
setUpdates(digests);
|
|
531
|
+
setChecking(false);
|
|
532
|
+
if (!silent) {
|
|
533
|
+
showToast(digests.length > 0 ? t("foundUpdates", { n: digests.length }) : t("allUpToDate"));
|
|
534
|
+
}
|
|
396
535
|
},
|
|
397
536
|
() => {
|
|
537
|
+
setChecking(false);
|
|
538
|
+
if (!silent) showToast(t("checkFail"), "error");
|
|
398
539
|
}
|
|
399
540
|
);
|
|
400
|
-
}, []);
|
|
541
|
+
}, [t]);
|
|
401
542
|
(0, import_react.useEffect)(() => {
|
|
402
543
|
void rpc("listInstalled").then(
|
|
403
544
|
(v) => {
|
|
@@ -408,27 +549,68 @@ function CenterPanel({ variant = "section" }) {
|
|
|
408
549
|
() => {
|
|
409
550
|
}
|
|
410
551
|
);
|
|
411
|
-
refreshUpdates();
|
|
552
|
+
refreshUpdates(true);
|
|
412
553
|
}, [refreshUpdates]);
|
|
554
|
+
(0, import_react.useEffect)(() => {
|
|
555
|
+
let alive = true;
|
|
556
|
+
let timer = null;
|
|
557
|
+
const poll = async () => {
|
|
558
|
+
if (!alive) return;
|
|
559
|
+
try {
|
|
560
|
+
const r = await rpc("listMarket", { source: "all" });
|
|
561
|
+
if (!alive) return;
|
|
562
|
+
marketCache.all = { plugins: r.plugins, done: r.done };
|
|
563
|
+
setMarketCount(r.plugins.length);
|
|
564
|
+
if (!r.done) timer = setTimeout(() => {
|
|
565
|
+
void poll();
|
|
566
|
+
}, 5e3);
|
|
567
|
+
} catch {
|
|
568
|
+
if (alive) timer = setTimeout(() => {
|
|
569
|
+
void poll();
|
|
570
|
+
}, 15e3);
|
|
571
|
+
}
|
|
572
|
+
};
|
|
573
|
+
void poll();
|
|
574
|
+
return () => {
|
|
575
|
+
alive = false;
|
|
576
|
+
if (timer !== null) clearTimeout(timer);
|
|
577
|
+
};
|
|
578
|
+
}, []);
|
|
413
579
|
const updateOne = (name) => {
|
|
414
580
|
setBusyUpdate(name);
|
|
415
581
|
void rpc("update", { name }).then(
|
|
416
|
-
() => {
|
|
582
|
+
(v) => {
|
|
583
|
+
if (v !== true) throw new Error(t("updateNotApplied"));
|
|
417
584
|
setBusyUpdate(null);
|
|
418
|
-
showToast(
|
|
585
|
+
showToast(t("updatedOne", { n: name }));
|
|
419
586
|
},
|
|
420
587
|
(e) => {
|
|
421
588
|
setBusyUpdate(null);
|
|
422
|
-
showToast(
|
|
589
|
+
showToast(t("updateFailed", { e: e instanceof Error ? e.message : String(e) }), "error");
|
|
423
590
|
}
|
|
424
591
|
);
|
|
425
592
|
};
|
|
426
|
-
const updateAll = () => {
|
|
427
|
-
if (updates
|
|
428
|
-
|
|
429
|
-
|
|
593
|
+
const updateAll = async () => {
|
|
594
|
+
if (updates === null || updates.length === 0) return;
|
|
595
|
+
setBusyUpdate("__all__");
|
|
596
|
+
let okCount = 0;
|
|
597
|
+
const failures = [];
|
|
598
|
+
for (const u of updates) {
|
|
599
|
+
try {
|
|
600
|
+
const v = await rpc("update", { name: u.name });
|
|
601
|
+
if (v !== true) throw new Error(t("updateNotApplied"));
|
|
602
|
+
okCount++;
|
|
603
|
+
} catch (e) {
|
|
604
|
+
failures.push(`${u.name}\uFF1A${e instanceof Error ? e.message : String(e)}`);
|
|
430
605
|
}
|
|
431
606
|
}
|
|
607
|
+
setBusyUpdate(null);
|
|
608
|
+
if (failures.length === 0) {
|
|
609
|
+
showToast(t("updatedMany", { n: okCount }));
|
|
610
|
+
} else {
|
|
611
|
+
showToast(t("updateSummary", { a: okCount, b: failures.length, c: failures.join("\uFF1B") }), "error");
|
|
612
|
+
}
|
|
613
|
+
refreshUpdates();
|
|
432
614
|
};
|
|
433
615
|
const tab = (v, label, count) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", { type: "button", className: `pc-tab${view === v ? " active" : ""}`, onClick: () => {
|
|
434
616
|
setView(v);
|
|
@@ -437,44 +619,37 @@ function CenterPanel({ variant = "section" }) {
|
|
|
437
619
|
count !== null && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "pc-count", children: count })
|
|
438
620
|
] }, v);
|
|
439
621
|
const tabs = /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "pc-tabs", children: [
|
|
440
|
-
tab("installed", "
|
|
441
|
-
tab("market", "
|
|
442
|
-
tab("updates", "
|
|
622
|
+
tab("installed", t("tabInstalled"), installedCount),
|
|
623
|
+
tab("market", t("tabMarket"), marketCount),
|
|
624
|
+
tab("updates", t("tabUpdates"), updates?.length ?? 0)
|
|
443
625
|
] });
|
|
444
626
|
const head = (showTitle) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "pc-head", children: [
|
|
445
|
-
showTitle && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "pc-title", children: "
|
|
446
|
-
/* @__PURE__ */ (0, import_jsx_runtime.
|
|
447
|
-
"\u5DF2\u5B89\u88C5 ",
|
|
448
|
-
installedCount,
|
|
449
|
-
" \xB7 \u6709\u66F4\u65B0 ",
|
|
450
|
-
updates?.length ?? 0,
|
|
451
|
-
" \xB7 \u5931\u6548 ",
|
|
452
|
-
failedCount
|
|
453
|
-
] }),
|
|
627
|
+
showTitle && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "pc-title", children: t("title") }),
|
|
628
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "pc-sub", children: t("headSummary", { a: installedCount, b: updates?.length ?? 0, c: failedCount }) }),
|
|
454
629
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "pc-spacer" }),
|
|
455
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: "pc-btn",
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
630
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: "pc-btn", disabled: checking, onClick: () => {
|
|
631
|
+
refreshUpdates();
|
|
632
|
+
}, children: checking ? t("checking") : t("check") }),
|
|
633
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: "pc-btn primary", disabled: !updates?.length || busyUpdate !== null, onClick: () => {
|
|
634
|
+
void updateAll();
|
|
635
|
+
}, children: t("updateAll", { n: updates?.length ?? 0 }) })
|
|
461
636
|
] });
|
|
462
637
|
const installedToolbar = view === "installed" ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "pc-toolbar", children: [
|
|
463
638
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", { className: "pc-search", value: search, onChange: (e) => {
|
|
464
639
|
setSearch(e.target.value);
|
|
465
|
-
}, placeholder: "
|
|
640
|
+
}, placeholder: t("searchInstalled") }),
|
|
466
641
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("select", { className: "pc-select", value: installedSource ?? "", onChange: (e) => {
|
|
467
642
|
setInstalledSource(e.target.value === "" ? null : e.target.value);
|
|
468
643
|
}, children: [
|
|
469
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "", children: "
|
|
470
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "official", children: "
|
|
471
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "installed", children: "
|
|
472
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "local", children: "
|
|
473
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "builtin", children: "
|
|
644
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "", children: t("allSources") }),
|
|
645
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "official", children: t("srcOfficial") }),
|
|
646
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "installed", children: t("srcInstalled") }),
|
|
647
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "local", children: t("srcLocal") }),
|
|
648
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "builtin", children: t("srcBuiltin") })
|
|
474
649
|
] }),
|
|
475
650
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: `pc-chip${installedCategory === null ? " active" : ""}`, onClick: () => {
|
|
476
651
|
setInstalledCategory(null);
|
|
477
|
-
}, children: "
|
|
652
|
+
}, children: t("all") }),
|
|
478
653
|
CATEGORIES.map((c) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: `pc-chip${installedCategory === c ? " active" : ""}`, onClick: () => {
|
|
479
654
|
setInstalledCategory(c);
|
|
480
655
|
}, children: c }, c))
|
|
@@ -482,23 +657,23 @@ function CenterPanel({ variant = "section" }) {
|
|
|
482
657
|
const marketToolbar = view === "market" ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "pc-toolbar", children: [
|
|
483
658
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", { className: "pc-search", value: marketSearch, onChange: (e) => {
|
|
484
659
|
setMarketSearch(e.target.value);
|
|
485
|
-
}, placeholder: "
|
|
660
|
+
}, placeholder: t("searchMarket") }),
|
|
486
661
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("select", { className: "pc-select", value: source, onChange: (e) => {
|
|
487
662
|
setSource(e.target.value);
|
|
488
663
|
setCategory(null);
|
|
489
664
|
}, children: [
|
|
490
665
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "awesome", children: "awesome-dsh-plugin" }),
|
|
491
666
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "oh-my-dsh", children: "Oh-My-DSH" }),
|
|
492
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "all", children: "
|
|
667
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "all", children: t("allMarkets") })
|
|
493
668
|
] }),
|
|
494
669
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: `pc-chip${category === null ? " active" : ""}`, onClick: () => {
|
|
495
670
|
setCategory(null);
|
|
496
|
-
}, children: "
|
|
671
|
+
}, children: t("all") }),
|
|
497
672
|
CATEGORIES.map((c) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: `pc-chip${category === c ? " active" : ""}`, onClick: () => {
|
|
498
673
|
setCategory(c);
|
|
499
674
|
}, children: c }, c)),
|
|
500
675
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "pc-spacer" }),
|
|
501
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", title: single ? "
|
|
676
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", title: single ? t("gridDouble") : t("gridSingle"), "aria-label": single ? t("gridDouble") : t("gridSingle"), className: "pc-iconbtn", onClick: () => {
|
|
502
677
|
setSingle((v) => !v);
|
|
503
678
|
}, children: single ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", { width: "14", height: "14", viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: "1.5", children: [
|
|
504
679
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("rect", { x: "2", y: "2", width: "5", height: "5", rx: "1" }),
|
|
@@ -537,7 +712,8 @@ function CenterPanel({ variant = "section" }) {
|
|
|
537
712
|
] });
|
|
538
713
|
}
|
|
539
714
|
function HeaderButton() {
|
|
540
|
-
|
|
715
|
+
const t = useT();
|
|
716
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", title: t("title"), "aria-label": t("title"), className: "pc-headerbtn", onClick: openOverlay, children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: "1.5", children: [
|
|
541
717
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("rect", { x: "2", y: "2", width: "5", height: "5", rx: "1" }),
|
|
542
718
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("rect", { x: "9", y: "2", width: "5", height: "5", rx: "1" }),
|
|
543
719
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("rect", { x: "2", y: "9", width: "5", height: "5", rx: "1" }),
|
|
@@ -545,13 +721,14 @@ function HeaderButton() {
|
|
|
545
721
|
] }) });
|
|
546
722
|
}
|
|
547
723
|
function OverlayPanel() {
|
|
724
|
+
const t = useT();
|
|
548
725
|
const open = useOverlayOpen();
|
|
549
726
|
if (!open) return null;
|
|
550
|
-
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "pc-overlay", role: "presentation", children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "pc-panel", role: "dialog", "aria-modal": "true", "aria-label": "
|
|
727
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "pc-overlay", role: "presentation", children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "pc-panel", role: "dialog", "aria-modal": "true", "aria-label": t("title"), children: [
|
|
551
728
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "pc-panel-head", children: [
|
|
552
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "pc-title", children: "
|
|
729
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "pc-title", children: t("title") }),
|
|
553
730
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "pc-spacer" }),
|
|
554
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "pc-close", onClick: closeOverlay, "aria-label": "
|
|
731
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "pc-close", onClick: closeOverlay, "aria-label": t("close"), children: "\u2715" })
|
|
555
732
|
] }),
|
|
556
733
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "pc-panel-body", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CenterPanel, { variant: "overlay" }) })
|
|
557
734
|
] }) });
|
|
@@ -562,17 +739,37 @@ function Toast() {
|
|
|
562
739
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: `pc-toast ${t.kind}`, children: t.message });
|
|
563
740
|
}
|
|
564
741
|
function WhatsNewDialog() {
|
|
742
|
+
const t = useT();
|
|
565
743
|
const open = useWhatsNewOpen();
|
|
744
|
+
const [busy, setBusy] = (0, import_react.useState)(false);
|
|
566
745
|
if (!open || whatsNewDigests.length === 0) return null;
|
|
567
|
-
|
|
746
|
+
const updateNow = async () => {
|
|
747
|
+
setBusy(true);
|
|
748
|
+
let okCount = 0;
|
|
749
|
+
const failures = [];
|
|
750
|
+
for (const u of whatsNewDigests) {
|
|
751
|
+
try {
|
|
752
|
+
const v = await rpc("update", { name: u.name });
|
|
753
|
+
if (v !== true) throw new Error(t("updateNotApplied"));
|
|
754
|
+
okCount++;
|
|
755
|
+
} catch (e) {
|
|
756
|
+
failures.push(`${u.name}\uFF1A${e instanceof Error ? e.message : String(e)}`);
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
setBusy(false);
|
|
760
|
+
if (failures.length === 0) {
|
|
761
|
+
showToast(t("updatedMany", { n: okCount }));
|
|
762
|
+
closeWhatsNew();
|
|
763
|
+
} else {
|
|
764
|
+
showToast(t("updateSummary", { a: okCount, b: failures.length, c: failures.join("\uFF1B") }), "error");
|
|
765
|
+
}
|
|
766
|
+
};
|
|
767
|
+
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": t("whatsNewTitle"), children: [
|
|
568
768
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "pc-panel-head", children: [
|
|
569
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "pc-title", children: "
|
|
570
|
-
/* @__PURE__ */ (0, import_jsx_runtime.
|
|
571
|
-
whatsNewDigests.length,
|
|
572
|
-
" \u4E2A\u63D2\u4EF6\u6709\u65B0\u7248\u672C"
|
|
573
|
-
] }),
|
|
769
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "pc-title", children: t("whatsNewTitle") }),
|
|
770
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "pc-sub", style: { marginTop: 0 }, children: t("whatsNewSub", { n: whatsNewDigests.length }) }),
|
|
574
771
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "pc-spacer" }),
|
|
575
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "pc-close", onClick: closeWhatsNew, "aria-label": "
|
|
772
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "pc-close", onClick: closeWhatsNew, "aria-label": t("close"), children: "\u2715" })
|
|
576
773
|
] }),
|
|
577
774
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "pc-panel-body", style: { overflow: "auto" }, children: [
|
|
578
775
|
whatsNewDigests.map((u) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "pc-wn-item", children: [
|
|
@@ -585,8 +782,11 @@ function WhatsNewDialog() {
|
|
|
585
782
|
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)) })
|
|
586
783
|
] }, u.name)),
|
|
587
784
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { display: "flex", justifyContent: "flex-end", gap: "8px", marginTop: "16px" }, children: [
|
|
588
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: "pc-btn", onClick: closeWhatsNew, children: "
|
|
589
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: "pc-btn
|
|
785
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: "pc-btn", onClick: closeWhatsNew, children: t("later") }),
|
|
786
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: "pc-btn", onClick: closeWhatsNew, children: t("markAllRead") }),
|
|
787
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: "pc-btn primary", disabled: busy, onClick: () => {
|
|
788
|
+
void updateNow();
|
|
789
|
+
}, children: busy ? t("updating") : t("updateNow") })
|
|
590
790
|
] })
|
|
591
791
|
] })
|
|
592
792
|
] }) });
|
|
@@ -599,11 +799,16 @@ function apply(ctx) {
|
|
|
599
799
|
if (result.ok) return result.value;
|
|
600
800
|
throw new Error(result.error?.message ?? `plugin-center: ${endpoint} failed`);
|
|
601
801
|
};
|
|
802
|
+
const initial = ctx.locale?.getLocale?.()?.active;
|
|
803
|
+
if (typeof initial === "string") adoptLocale(initial);
|
|
804
|
+
ctx.on?.("locale/change", (snap) => {
|
|
805
|
+
adoptLocale(snap?.active);
|
|
806
|
+
});
|
|
602
807
|
ctx.slots.inject("settings.section", () => ctx.slots.register({
|
|
603
808
|
name: "settings.section",
|
|
604
809
|
id: "plugin-center",
|
|
605
810
|
order: 50,
|
|
606
|
-
label: () =>
|
|
811
|
+
label: () => STRINGS[localeId].title
|
|
607
812
|
}, CenterPanel));
|
|
608
813
|
ctx.slots.inject("conversation.session.header.utilities", () => ctx.slots.register({
|
|
609
814
|
name: "conversation.session.header.utilities",
|
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;
|
|
@@ -63,8 +65,16 @@ export declare class PluginCenterEngine extends Service {
|
|
|
63
65
|
listMarket(source?: MarketSource): Promise<MarketSnapshot>;
|
|
64
66
|
/** Detect updates for every installed third-party/local plugin, TTL-cached. */
|
|
65
67
|
checkUpdates(sinceIso: string): Promise<UpdateDigest[]>;
|
|
66
|
-
install(spec: string): Promise<
|
|
67
|
-
|
|
68
|
+
install(spec: string): Promise<{
|
|
69
|
+
ok: boolean;
|
|
70
|
+
detail: string;
|
|
71
|
+
}>;
|
|
72
|
+
update(name: string): Promise<{
|
|
73
|
+
ok: boolean;
|
|
74
|
+
detail: string;
|
|
75
|
+
}>;
|
|
76
|
+
/** 串行执行一次 pnpm 操作并失效缓存(无论成败都放行链条后续任务)。 */
|
|
77
|
+
private enqueuePnpm;
|
|
68
78
|
/** Temporary diagnostics for the empty-update bug; removed once root-caused. */
|
|
69
79
|
debug(): Promise<{
|
|
70
80
|
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 result = await op();
|
|
235
|
+
this.installedNamesCache = null;
|
|
236
|
+
this.updatesCache = null;
|
|
237
|
+
return result;
|
|
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/dist/rpc.js
CHANGED
|
@@ -28,13 +28,19 @@ export class PluginCenterRpc extends Service {
|
|
|
28
28
|
const spec = payload?.spec;
|
|
29
29
|
if (typeof spec !== 'string' || spec === '')
|
|
30
30
|
return internal('install: spec is required');
|
|
31
|
-
|
|
31
|
+
const result = await ctx.pluginCenter.install(spec);
|
|
32
|
+
if (!result.ok)
|
|
33
|
+
return internal(`install ${spec} 失败:${result.detail}`);
|
|
34
|
+
return { ok: true, value: true };
|
|
32
35
|
}
|
|
33
36
|
case 'update': {
|
|
34
37
|
const name = payload?.name;
|
|
35
38
|
if (typeof name !== 'string' || name === '')
|
|
36
39
|
return internal('update: name is required');
|
|
37
|
-
|
|
40
|
+
const result = await ctx.pluginCenter.update(name);
|
|
41
|
+
if (!result.ok)
|
|
42
|
+
return internal(`update ${name} 失败:${result.detail}`);
|
|
43
|
+
return { ok: true, value: true };
|
|
38
44
|
}
|
|
39
45
|
case 'debug':
|
|
40
46
|
return { ok: true, value: await ctx.pluginCenter.debug() };
|
package/dist/update.d.ts
CHANGED
|
@@ -17,14 +17,22 @@ export declare function fetchCommitChangelog(repoUrl: string | null, sinceIso: s
|
|
|
17
17
|
* DSH version via the plugin's peer-dependency range.
|
|
18
18
|
*/
|
|
19
19
|
export declare function detectUpdate(name: string, localVersion: string, repoUrl: string | null, compatRange: string | null, localDshVersion: string, sinceIso: string): Promise<UpdateDigest | null>;
|
|
20
|
+
/** One pnpm invocation result: success plus a failure detail for diagnostics. */
|
|
21
|
+
export interface PnpmResult {
|
|
22
|
+
ok: boolean;
|
|
23
|
+
detail: string;
|
|
24
|
+
}
|
|
25
|
+
/** pnpm 候选命令:GUI 进程 PATH 常缺用户级 npm 全局目录;且 Windows 上
|
|
26
|
+
* CreateProcess 只找 pnpm.exe(.cmd/.ps1 必须经 shell)——2026-08-17 实测
|
|
27
|
+
* spawn('pnpm', shell:false) 直接 ENOENT,更新永远假成功。 */
|
|
28
|
+
export declare function pnpmCandidates(): string[];
|
|
20
29
|
/**
|
|
21
|
-
* Run pnpm in the profile directory
|
|
22
|
-
*
|
|
23
|
-
* the only result this layer needs.
|
|
24
|
-
* @returns the child exit code, or 1 when spawn itself fails.
|
|
30
|
+
* Run pnpm in the profile directory, trying each candidate command in turn.
|
|
31
|
+
* Output inherits the process stdio (no pipe capture — the host sandbox
|
|
32
|
+
* forbids named-pipe stdio); the exit code is the only result this layer needs.
|
|
25
33
|
*/
|
|
26
|
-
export declare function runPnpm(args: readonly string[], cwd: string): Promise<
|
|
34
|
+
export declare function runPnpm(args: readonly string[], cwd: string): Promise<PnpmResult>;
|
|
27
35
|
/** Install a package into the web profile, mirroring `dsh plugin add` semantics. */
|
|
28
|
-
export declare function installPlugin(packageSpec: string, profileDir: string): Promise<
|
|
36
|
+
export declare function installPlugin(packageSpec: string, profileDir: string): Promise<PnpmResult>;
|
|
29
37
|
/** Update a package to latest, mirroring `dsh plugin add <pkg>` (pnpm installs latest). */
|
|
30
|
-
export declare function updatePlugin(packageName: string, profileDir: string): Promise<
|
|
38
|
+
export declare function updatePlugin(packageName: string, profileDir: string): Promise<PnpmResult>;
|
package/dist/update.js
CHANGED
|
@@ -4,6 +4,9 @@
|
|
|
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 { existsSync } from 'node:fs';
|
|
8
|
+
import { homedir } from 'node:os';
|
|
9
|
+
import { join } from 'node:path';
|
|
7
10
|
import { compareVersions, satisfies } from "./semver.js";
|
|
8
11
|
const UA = { 'User-Agent': 'dsh-plugin-center' };
|
|
9
12
|
/** Latest published version on the npm registry; null when unreachable/unpublished. */
|
|
@@ -67,34 +70,54 @@ export async function detectUpdate(name, localVersion, repoUrl, compatRange, loc
|
|
|
67
70
|
compatRange,
|
|
68
71
|
};
|
|
69
72
|
}
|
|
70
|
-
/**
|
|
71
|
-
*
|
|
72
|
-
*
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
73
|
+
/** pnpm 候选命令:GUI 进程 PATH 常缺用户级 npm 全局目录;且 Windows 上
|
|
74
|
+
* CreateProcess 只找 pnpm.exe(.cmd/.ps1 必须经 shell)——2026-08-17 实测
|
|
75
|
+
* spawn('pnpm', shell:false) 直接 ENOENT,更新永远假成功。 */
|
|
76
|
+
export function pnpmCandidates() {
|
|
77
|
+
const commands = ['pnpm', 'pnpm.cmd'];
|
|
78
|
+
const userNpm = join(homedir(), 'AppData', 'Roaming', 'npm');
|
|
79
|
+
for (const name of ['pnpm.cmd', 'pnpm.exe', 'pnpm']) {
|
|
80
|
+
const candidate = join(userNpm, name);
|
|
81
|
+
if (existsSync(candidate))
|
|
82
|
+
commands.push(candidate);
|
|
83
|
+
}
|
|
84
|
+
return commands;
|
|
85
|
+
}
|
|
86
|
+
function runOne(command, args, cwd) {
|
|
77
87
|
return new Promise((resolve) => {
|
|
78
88
|
let child;
|
|
79
89
|
try {
|
|
80
|
-
|
|
90
|
+
// shell: true —— Windows 下 .cmd shim 必须经 shell 才能 spawn。
|
|
91
|
+
child = spawn(command, [...args], { cwd, stdio: 'inherit', shell: true });
|
|
81
92
|
}
|
|
82
|
-
catch {
|
|
83
|
-
resolve(
|
|
93
|
+
catch (e) {
|
|
94
|
+
resolve({ ok: false, detail: e instanceof Error ? e.message : String(e) });
|
|
84
95
|
return;
|
|
85
96
|
}
|
|
86
|
-
child.on('error',
|
|
87
|
-
child.on('close', code => resolve(code ?? 1));
|
|
97
|
+
child.on('error', e => resolve({ ok: false, detail: e.message }));
|
|
98
|
+
child.on('close', code => resolve(code === 0 ? { ok: true, detail: '' } : { ok: false, detail: `exit code ${code ?? 1}` }));
|
|
88
99
|
});
|
|
89
100
|
}
|
|
101
|
+
/**
|
|
102
|
+
* Run pnpm in the profile directory, trying each candidate command in turn.
|
|
103
|
+
* Output inherits the process stdio (no pipe capture — the host sandbox
|
|
104
|
+
* forbids named-pipe stdio); the exit code is the only result this layer needs.
|
|
105
|
+
*/
|
|
106
|
+
export async function runPnpm(args, cwd) {
|
|
107
|
+
let last = { ok: false, detail: 'no pnpm candidate found' };
|
|
108
|
+
for (const command of pnpmCandidates()) {
|
|
109
|
+
last = await runOne(command, args, cwd);
|
|
110
|
+
if (last.ok)
|
|
111
|
+
return last;
|
|
112
|
+
}
|
|
113
|
+
return last;
|
|
114
|
+
}
|
|
90
115
|
/** Install a package into the web profile, mirroring `dsh plugin add` semantics. */
|
|
91
116
|
export async function installPlugin(packageSpec, profileDir) {
|
|
92
117
|
// `-w` is required: every profile ships a pnpm-workspace.yaml.
|
|
93
|
-
|
|
94
|
-
return code === 0;
|
|
118
|
+
return runPnpm(['add', '-w', packageSpec], profileDir);
|
|
95
119
|
}
|
|
96
120
|
/** Update a package to latest, mirroring `dsh plugin add <pkg>` (pnpm installs latest). */
|
|
97
121
|
export async function updatePlugin(packageName, profileDir) {
|
|
98
|
-
|
|
99
|
-
return code === 0;
|
|
122
|
+
return runPnpm(['add', '-w', packageName], profileDir);
|
|
100
123
|
}
|
package/package.json
CHANGED