dsh-plugin 2.2.7 → 2.3.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/client/client.js +422 -338
- package/client/client.js.map +1 -1
- package/lib/http/catalog.js +3 -1
- package/lib/services/probe.js +23 -7
- package/lib/types/services/probe.d.ts +1 -0
- package/package.json +1 -1
- package/src/client/components/catalog/PluginCard.tsx +26 -6
- package/src/client/components/modals/HubUpdateModal.tsx +21 -8
- package/src/client/components/modals/NotificationsModal.tsx +5 -0
- package/src/client/components/modals/modals.tsx +4 -0
- package/src/client/components/views/InstalledView.tsx +62 -24
- package/src/client/locales.ts +30 -12
- package/src/client/logic/failures.ts +18 -1
- package/src/client/logic/urls.ts +4 -1
- package/src/client/styles/InstalledView.module.css +72 -19
- package/src/client/styles/List.module.css +33 -3
- package/src/client/styles/Modal.module.css +42 -5
- package/src/server/http/catalog.ts +3 -1
- package/src/server/services/probe.ts +24 -8
|
@@ -221,7 +221,7 @@
|
|
|
221
221
|
background: var(--hub-text-tertiary);
|
|
222
222
|
}
|
|
223
223
|
|
|
224
|
-
/* 主信息:名称 + 已装版本 +
|
|
224
|
+
/* 主信息:名称 + 已装版本 + 有更新徽标(同一行,名称超长时省略,徽标不被挤走) */
|
|
225
225
|
.rowMain {
|
|
226
226
|
display: flex;
|
|
227
227
|
align-items: baseline;
|
|
@@ -230,25 +230,56 @@
|
|
|
230
230
|
flex: 1 1 auto;
|
|
231
231
|
}
|
|
232
232
|
|
|
233
|
+
/* 右上角 Star / Fork:靠右单行,配色与插件市场列表一致(星标琥珀、Fork 次级灰) */
|
|
234
|
+
.rowStats {
|
|
235
|
+
display: flex;
|
|
236
|
+
align-items: center;
|
|
237
|
+
gap: 10px;
|
|
238
|
+
margin-left: auto;
|
|
239
|
+
flex-shrink: 0;
|
|
240
|
+
white-space: nowrap;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
.rowStar {
|
|
244
|
+
font-size: 11px;
|
|
245
|
+
line-height: 16px;
|
|
246
|
+
color: var(--hub-warn);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
.rowFork {
|
|
250
|
+
font-size: 11px;
|
|
251
|
+
line-height: 16px;
|
|
252
|
+
color: var(--hub-text-tertiary);
|
|
253
|
+
}
|
|
254
|
+
|
|
233
255
|
.rowTitle {
|
|
234
256
|
font-size: 12px;
|
|
235
257
|
line-height: 18px;
|
|
236
258
|
font-weight: 600;
|
|
237
259
|
color: var(--hub-text-primary);
|
|
260
|
+
flex: 0 1 auto;
|
|
238
261
|
min-width: 0;
|
|
239
262
|
white-space: nowrap;
|
|
240
263
|
overflow: hidden;
|
|
241
264
|
text-overflow: ellipsis;
|
|
265
|
+
/* 名称是主站详情页链接:不做成蓝色下划线,悬停才点明可点 */
|
|
266
|
+
text-decoration: none;
|
|
242
267
|
}
|
|
243
268
|
|
|
244
|
-
|
|
269
|
+
a.rowTitle:hover {
|
|
270
|
+
color: var(--hub-brand);
|
|
271
|
+
text-decoration: underline;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/* 已装版本:等宽弱化小字。描边透明但保留 1px,与同行的「有更新」「插件市场」等高 */
|
|
245
275
|
.versionBadge {
|
|
246
276
|
flex-shrink: 0;
|
|
247
277
|
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
|
248
|
-
font-size:
|
|
249
|
-
line-height:
|
|
278
|
+
font-size: 10px;
|
|
279
|
+
line-height: 14px;
|
|
250
280
|
color: var(--hub-text-tertiary);
|
|
251
281
|
background: var(--hub-bg-hover);
|
|
282
|
+
border: 1px solid transparent;
|
|
252
283
|
border-radius: 4px;
|
|
253
284
|
padding: 0 5px;
|
|
254
285
|
}
|
|
@@ -256,8 +287,8 @@
|
|
|
256
287
|
/* 有更新徽标:品牌色实底小圆标 */
|
|
257
288
|
.updateBadge {
|
|
258
289
|
flex-shrink: 0;
|
|
259
|
-
font-size:
|
|
260
|
-
line-height:
|
|
290
|
+
font-size: 10px;
|
|
291
|
+
line-height: 14px;
|
|
261
292
|
font-weight: 500;
|
|
262
293
|
color: var(--hub-warn);
|
|
263
294
|
background: var(--hub-warn-tint);
|
|
@@ -274,8 +305,8 @@
|
|
|
274
305
|
width: 60px;
|
|
275
306
|
box-sizing: border-box;
|
|
276
307
|
text-align: center;
|
|
277
|
-
font-size:
|
|
278
|
-
line-height:
|
|
308
|
+
font-size: 10px;
|
|
309
|
+
line-height: 14px;
|
|
279
310
|
font-weight: 500;
|
|
280
311
|
border-radius: 4px;
|
|
281
312
|
padding: 0 4px;
|
|
@@ -300,8 +331,8 @@
|
|
|
300
331
|
与「有更新」实底区分,表达「这是示例项目,宿主不加载」 */
|
|
301
332
|
.exampleBadge {
|
|
302
333
|
flex-shrink: 0;
|
|
303
|
-
font-size:
|
|
304
|
-
line-height:
|
|
334
|
+
font-size: 10px;
|
|
335
|
+
line-height: 14px;
|
|
305
336
|
font-weight: 500;
|
|
306
337
|
color: var(--hub-brand);
|
|
307
338
|
background: var(--hub-brand-tint);
|
|
@@ -338,16 +369,34 @@
|
|
|
338
369
|
white-space: nowrap;
|
|
339
370
|
overflow: hidden;
|
|
340
371
|
text-overflow: ellipsis;
|
|
372
|
+
/* 仓库名是主站详情页链接:默认与普通文字无异,悬停才提示可点 */
|
|
373
|
+
text-decoration: none;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
a.rowRepo:hover {
|
|
377
|
+
color: var(--hub-brand);
|
|
378
|
+
text-decoration: underline;
|
|
341
379
|
}
|
|
342
380
|
|
|
343
381
|
.rowCategory {
|
|
344
382
|
flex-shrink: 0;
|
|
345
|
-
font-size:
|
|
346
|
-
line-height:
|
|
383
|
+
font-size: 10px;
|
|
384
|
+
line-height: 14px;
|
|
347
385
|
color: var(--hub-text-tertiary);
|
|
348
386
|
background: var(--hub-bg-hover);
|
|
387
|
+
border: 1px solid transparent;
|
|
349
388
|
border-radius: 4px;
|
|
350
|
-
padding: 0
|
|
389
|
+
padding: 0 5px;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
/* 来源标签 + 操作按钮行:独立成行靠右成组,名称行只留名称/版本/更新状态 */
|
|
393
|
+
.rowActionLine {
|
|
394
|
+
display: flex;
|
|
395
|
+
align-items: center;
|
|
396
|
+
justify-content: flex-end;
|
|
397
|
+
gap: 8px;
|
|
398
|
+
padding-left: 16px;
|
|
399
|
+
min-width: 0;
|
|
351
400
|
}
|
|
352
401
|
|
|
353
402
|
/* —— 行操作:详情 / 更新 / 卸载 —— */
|
|
@@ -359,14 +408,16 @@
|
|
|
359
408
|
flex-shrink: 0;
|
|
360
409
|
}
|
|
361
410
|
|
|
362
|
-
/*
|
|
411
|
+
/* 详情:默认无边框(低频操作不抢视觉),悬停才出现描边与底色成为按钮形状。
|
|
412
|
+
描边保留 1px 透明而非 none,保证与同行的更新/卸载按钮同高、文字基线对齐 */
|
|
363
413
|
.rowDetail {
|
|
364
414
|
font-family: inherit;
|
|
365
415
|
font-size: 12px;
|
|
366
416
|
line-height: 18px;
|
|
417
|
+
font-weight: 500;
|
|
367
418
|
color: var(--hub-text-secondary);
|
|
368
419
|
background: transparent;
|
|
369
|
-
border: 1px solid
|
|
420
|
+
border: 1px solid transparent;
|
|
370
421
|
border-radius: 6px;
|
|
371
422
|
padding: 2px 10px;
|
|
372
423
|
cursor: pointer;
|
|
@@ -376,7 +427,7 @@
|
|
|
376
427
|
|
|
377
428
|
.rowDetail:hover {
|
|
378
429
|
color: var(--hub-text-primary);
|
|
379
|
-
border-color: var(--hub-border-
|
|
430
|
+
border-color: var(--hub-border-2);
|
|
380
431
|
background: var(--hub-bg-hover);
|
|
381
432
|
}
|
|
382
433
|
|
|
@@ -403,7 +454,8 @@
|
|
|
403
454
|
border-color: var(--hub-warning);
|
|
404
455
|
}
|
|
405
456
|
|
|
406
|
-
/*
|
|
457
|
+
/* 更新:琥珀色实底小按钮(与插件市场「可更新」同色),仅在「有更新」时出现。
|
|
458
|
+
与详情/卸载同度量(1px 描边 + 2px 内距),否则实底按钮会比描边按钮窄一圈 */
|
|
407
459
|
.rowUpdate {
|
|
408
460
|
font-family: inherit;
|
|
409
461
|
font-size: 12px;
|
|
@@ -411,9 +463,9 @@
|
|
|
411
463
|
font-weight: 500;
|
|
412
464
|
color: #fff;
|
|
413
465
|
background: var(--hub-warn);
|
|
414
|
-
border:
|
|
466
|
+
border: 1px solid var(--hub-warn);
|
|
415
467
|
border-radius: 6px;
|
|
416
|
-
padding:
|
|
468
|
+
padding: 2px 10px;
|
|
417
469
|
cursor: pointer;
|
|
418
470
|
user-select: none;
|
|
419
471
|
transition: background 120ms ease;
|
|
@@ -428,6 +480,7 @@
|
|
|
428
480
|
font-family: inherit;
|
|
429
481
|
font-size: 12px;
|
|
430
482
|
line-height: 18px;
|
|
483
|
+
font-weight: 500;
|
|
431
484
|
color: var(--hub-danger);
|
|
432
485
|
background: transparent;
|
|
433
486
|
border: 1px solid var(--hub-danger-border-soft);
|
|
@@ -52,7 +52,8 @@
|
|
|
52
52
|
gap: 4px;
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
-
|
|
55
|
+
/* 名称行:名称可收缩到省略号,版本与更新徽标不收缩 —— 长仓库名不会把状态挤没 */
|
|
56
|
+
.cardTitleRow {
|
|
56
57
|
display: flex;
|
|
57
58
|
align-items: center;
|
|
58
59
|
gap: 6px;
|
|
@@ -60,26 +61,55 @@
|
|
|
60
61
|
}
|
|
61
62
|
|
|
62
63
|
.cardTitle {
|
|
64
|
+
flex: 0 1 auto;
|
|
65
|
+
min-width: 0;
|
|
63
66
|
font-size: 13px;
|
|
64
67
|
line-height: 18px;
|
|
65
68
|
font-weight: 600;
|
|
66
69
|
white-space: nowrap;
|
|
67
70
|
overflow: hidden;
|
|
68
71
|
text-overflow: ellipsis;
|
|
72
|
+
/* 链接形态但不做成蓝色下划线:沿用标题颜色,悬停才点明可点 */
|
|
73
|
+
color: inherit;
|
|
74
|
+
text-decoration: none;
|
|
75
|
+
transition: color 120ms ease;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
a.cardTitle:hover {
|
|
79
|
+
color: var(--hub-brand);
|
|
80
|
+
text-decoration: underline;
|
|
69
81
|
}
|
|
70
82
|
|
|
83
|
+
/* 分类 / 验证徽标行:与名称行分开,长名称与多枚徽标互不抢宽度 */
|
|
84
|
+
.cardBadgeRow {
|
|
85
|
+
display: flex;
|
|
86
|
+
align-items: center;
|
|
87
|
+
flex-wrap: wrap;
|
|
88
|
+
gap: 6px;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/* 徽章统一度量:字号 / 行高 / 内距 / 描边一致,同屏并排时不会一大一小。
|
|
92
|
+
以 14px 行高 + 1px 描边收成 16px 高的轻量标签,不与名称行争视觉重量 */
|
|
71
93
|
.categoryBadge,
|
|
72
94
|
.verified,
|
|
95
|
+
.authorBadge,
|
|
73
96
|
.versionBadge,
|
|
74
97
|
.updateBadge {
|
|
75
98
|
flex-shrink: 0;
|
|
76
99
|
font-size: 10px;
|
|
77
|
-
line-height:
|
|
100
|
+
line-height: 14px;
|
|
78
101
|
border-radius: 4px;
|
|
79
|
-
padding: 0
|
|
102
|
+
padding: 0 5px;
|
|
80
103
|
border: 1px solid transparent;
|
|
81
104
|
}
|
|
82
105
|
|
|
106
|
+
/* 作者:等宽字体 + 中性灰,与分类(品牌蓝)、已验证(绿)区分,避免一行里多个彩色标签抢注意力 */
|
|
107
|
+
.authorBadge {
|
|
108
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
|
109
|
+
color: var(--hub-text-tertiary);
|
|
110
|
+
border-color: var(--hub-border-ghost);
|
|
111
|
+
}
|
|
112
|
+
|
|
83
113
|
.categoryBadge {
|
|
84
114
|
color: var(--hub-brand);
|
|
85
115
|
border-color: var(--hub-border-ghost);
|
|
@@ -1487,17 +1487,54 @@ a.failRepo:hover .failRepoUrl {
|
|
|
1487
1487
|
max-width: calc(100vw - 48px);
|
|
1488
1488
|
}
|
|
1489
1489
|
|
|
1490
|
-
/*
|
|
1491
|
-
|
|
1490
|
+
/* 版本条:版本号是这一屏的主信息,状态胶囊紧跟其后,发布时间靠右作为附属说明。
|
|
1491
|
+
三者在同一行、字号与颜色分层,用户一眼就能分清「版本号」与「状态」 */
|
|
1492
|
+
.hubVersionBar {
|
|
1492
1493
|
display: flex;
|
|
1493
1494
|
align-items: center;
|
|
1494
1495
|
flex-wrap: wrap;
|
|
1495
|
-
gap: 4px
|
|
1496
|
-
|
|
1496
|
+
gap: 4px 10px;
|
|
1497
|
+
}
|
|
1498
|
+
|
|
1499
|
+
.hubVersionNumber {
|
|
1500
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
|
1501
|
+
font-size: 18px;
|
|
1502
|
+
font-weight: 600;
|
|
1503
|
+
line-height: 26px;
|
|
1504
|
+
color: var(--hub-text-primary);
|
|
1505
|
+
}
|
|
1506
|
+
|
|
1507
|
+
/* 状态胶囊:颜色本身就是区分,读不到文案也能分辨最新 / 可更新 / 暂缓 */
|
|
1508
|
+
.hubStateChip {
|
|
1509
|
+
padding: 0 8px;
|
|
1510
|
+
border: 1px solid transparent;
|
|
1511
|
+
border-radius: 999px;
|
|
1512
|
+
font-size: 11px;
|
|
1497
1513
|
line-height: 18px;
|
|
1514
|
+
white-space: nowrap;
|
|
1515
|
+
}
|
|
1516
|
+
|
|
1517
|
+
.hubStateLatest {
|
|
1518
|
+
color: var(--hub-success);
|
|
1519
|
+
border-color: var(--hub-success-border);
|
|
1520
|
+
background: var(--hub-success-tint);
|
|
1521
|
+
}
|
|
1522
|
+
|
|
1523
|
+
.hubStateAvailable {
|
|
1524
|
+
color: var(--hub-warn);
|
|
1525
|
+
border-color: var(--hub-warn-border);
|
|
1526
|
+
background: var(--hub-warn-tint);
|
|
1498
1527
|
}
|
|
1499
1528
|
|
|
1500
|
-
.
|
|
1529
|
+
.hubStatePending {
|
|
1530
|
+
color: var(--hub-text-tertiary);
|
|
1531
|
+
border-color: var(--hub-border-2);
|
|
1532
|
+
}
|
|
1533
|
+
|
|
1534
|
+
.hubVersionDate {
|
|
1535
|
+
margin-left: auto;
|
|
1536
|
+
font-size: 12px;
|
|
1537
|
+
line-height: 18px;
|
|
1501
1538
|
color: var(--hub-text-tertiary);
|
|
1502
1539
|
}
|
|
1503
1540
|
|
|
@@ -148,7 +148,9 @@ export function registerCatalogRoutes(webServer: WebServerService, profile: stri
|
|
|
148
148
|
// 兜底缓存也损坏:落到下面的 502
|
|
149
149
|
}
|
|
150
150
|
}
|
|
151
|
-
|
|
151
|
+
// 带上 curl 退出码与 stderr 末行:只回 error 字段时,用户贴出来的响应里
|
|
152
|
+
// 没有任何可诊断信息
|
|
153
|
+
sendJson(response, 502, { error: 'catalog fetch failed', detail: r.detail })
|
|
152
154
|
return
|
|
153
155
|
}
|
|
154
156
|
try {
|
|
@@ -287,13 +287,29 @@ export async function finalUrl(url: string, proxy: string, timeoutMs: number): P
|
|
|
287
287
|
*/
|
|
288
288
|
export function fetchViaCurl(
|
|
289
289
|
url: string, proxy: string, timeoutMs: number,
|
|
290
|
-
): Promise<{ ok: boolean; body: string; reason: ProbeFailReason | null }> {
|
|
290
|
+
): Promise<{ ok: boolean; body: string; reason: ProbeFailReason | null; detail: string | null }> {
|
|
291
291
|
// 同样用 -sS:正文之外还要 stderr 里的 TLS 失败线索(被 stderr 收集,不外泄给用户)。
|
|
292
|
-
// --compressed
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
292
|
+
// 编码不用 --compressed 的自动协商,而是明确只接受 gzip:协商会让服务端按本机 curl 声明的
|
|
293
|
+
// 能力回 br / zstd,解不开时 curl 非零退出且正文为空,目录拉取直接失败;而 gzip 的解码器
|
|
294
|
+
// 每个 curl 都有,对 JSON 的压缩率也够。
|
|
295
|
+
const gzip = ['-H', 'Accept-Encoding: gzip', '--compressed']
|
|
296
|
+
const attempt = (extra: string[]): string[] => [
|
|
297
|
+
'-sS', ...gzip, ...extra, '--max-time', String(Math.max(1, Math.round(timeoutMs / 1000))), url,
|
|
298
|
+
]
|
|
299
|
+
return curlWithTlsFallback(attempt([]), proxyEnv(proxy), timeoutMs).then(async (first) => {
|
|
300
|
+
// 首轮失败(连接被重置 / 编码解不开 / HTTP/2 流异常)时退到 HTTP/1.1 再试一次:
|
|
301
|
+
// Windows 自带 curl 与大响应 + HTTP/2 的组合偶发流级失败,换协议版本代价最小;
|
|
302
|
+
// 只在真失败时才多一次请求。
|
|
303
|
+
const r = first.code === 0
|
|
304
|
+
? first
|
|
305
|
+
: await curlWithTlsFallback(attempt(['--http1.1']), proxyEnv(proxy), timeoutMs)
|
|
306
|
+
const failed = r.code !== 0 || r.stdout === ''
|
|
307
|
+
return {
|
|
308
|
+
ok: !failed,
|
|
309
|
+
body: r.stdout,
|
|
310
|
+
reason: failed && tlsRevocationBlocked(r.stderr) ? 'tlsRevocation' : null,
|
|
311
|
+
// 只留退出码与 stderr 末行:正文不进错误详情,且要够短才能在界面上完整显示
|
|
312
|
+
detail: failed ? `curl exit ${r.code ?? 'null'}: ${r.stderr.trim().split('\n').slice(-1)[0] ?? ''}`.slice(0, 300) : null,
|
|
313
|
+
}
|
|
314
|
+
})
|
|
299
315
|
}
|