dsh-plugin 0.1.1 → 1.0.0

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.
Files changed (64) hide show
  1. package/README.en.md +49 -26
  2. package/README.md +49 -26
  3. package/client/client.js +2148 -229
  4. package/client/client.js.map +1 -1
  5. package/lib/http/routes.js +396 -0
  6. package/lib/index.js +17 -8
  7. package/lib/services/install-types.js +11 -0
  8. package/lib/services/install.js +4 -0
  9. package/lib/services/installed-versions.js +51 -0
  10. package/lib/services/loader.js +98 -0
  11. package/lib/services/pending-restart.js +34 -0
  12. package/lib/services/preflight.js +94 -0
  13. package/lib/services/profile.js +88 -0
  14. package/lib/services/progress.js +50 -0
  15. package/lib/services/task-queue.js +417 -0
  16. package/lib/types/http/routes.d.ts +23 -0
  17. package/lib/types/index.d.ts +27 -0
  18. package/lib/types/services/install-types.d.ts +63 -0
  19. package/lib/types/services/install.d.ts +18 -0
  20. package/lib/types/services/installed-versions.d.ts +14 -0
  21. package/lib/types/services/loader.d.ts +43 -0
  22. package/lib/types/services/pending-restart.d.ts +13 -0
  23. package/lib/types/services/preflight.d.ts +6 -0
  24. package/lib/types/services/profile.d.ts +24 -0
  25. package/lib/types/services/progress.d.ts +17 -0
  26. package/lib/types/services/task-queue.d.ts +76 -0
  27. package/package.json +27 -6
  28. package/src/client/components/CatalogControls.tsx +104 -0
  29. package/src/client/components/CatalogHeader.tsx +78 -0
  30. package/src/client/components/CatalogList.tsx +75 -0
  31. package/src/client/components/CategoryTabs.tsx +35 -0
  32. package/src/client/components/Dropdown.tsx +80 -0
  33. package/src/client/components/NotificationsModal.tsx +230 -0
  34. package/src/client/components/PluginCard.tsx +104 -0
  35. package/src/client/components/PluginHubSection.tsx +339 -0
  36. package/src/client/components/ProgressView.tsx +23 -0
  37. package/src/client/components/icons.tsx +125 -0
  38. package/src/client/components/modals.tsx +365 -0
  39. package/src/client/hooks/useCatalog.ts +192 -0
  40. package/src/client/hooks/useTaskQueue.ts +405 -0
  41. package/src/client/index.tsx +8 -422
  42. package/src/client/lib/catalog.ts +204 -0
  43. package/src/client/lib/env.ts +16 -0
  44. package/src/client/lib/failures.ts +153 -0
  45. package/src/client/lib/format.ts +22 -0
  46. package/src/client/locales.ts +186 -18
  47. package/src/client/styles/Dropdown.module.css +123 -0
  48. package/src/client/styles/Header.module.css +432 -0
  49. package/src/client/styles/List.module.css +324 -0
  50. package/src/client/styles/Modal.module.css +965 -0
  51. package/src/client/styles/tokens.module.css +86 -0
  52. package/src/client/types.ts +80 -0
  53. package/src/server/http/routes.ts +397 -0
  54. package/src/server/index.ts +35 -0
  55. package/src/server/services/install-types.ts +63 -0
  56. package/src/server/services/install.ts +18 -0
  57. package/src/server/services/installed-versions.ts +65 -0
  58. package/src/server/services/loader.ts +110 -0
  59. package/src/server/services/pending-restart.ts +36 -0
  60. package/src/server/services/preflight.ts +97 -0
  61. package/src/server/services/profile.ts +89 -0
  62. package/src/server/services/progress.ts +48 -0
  63. package/src/server/services/task-queue.ts +450 -0
  64. package/src/client/Section.module.css +0 -589
@@ -1,589 +0,0 @@
1
- /* DSH-Plugin Hub section styles. Colors resolve through the host's
2
- * --dsw-alias-* design tokens with the original light-mode literal as
3
- * fallback, so the section follows the active theme. */
4
- .root {
5
- height: 100%;
6
- min-width: 0;
7
- display: flex;
8
- flex-direction: column;
9
- gap: 8px;
10
- color: var(--dsw-alias-label-primary, #1f2328);
11
- }
12
-
13
- /* —— header —— */
14
-
15
- .header {
16
- display: flex;
17
- align-items: center;
18
- justify-content: space-between;
19
- gap: 12px;
20
- flex-wrap: wrap;
21
- padding: 2px 2px 0;
22
- }
23
-
24
- .brand {
25
- display: flex;
26
- align-items: center;
27
- gap: 10px;
28
- min-width: 0;
29
- }
30
-
31
- .brandText {
32
- display: flex;
33
- flex-direction: column;
34
- gap: 1px;
35
- min-width: 0;
36
- }
37
-
38
- .title {
39
- font-size: 14px;
40
- line-height: 20px;
41
- font-weight: 600;
42
- margin: 0;
43
- }
44
-
45
- .tagline {
46
- font-size: 12px;
47
- line-height: 18px;
48
- color: var(--dsw-alias-label-tertiary, #8b93a1);
49
- margin: 0;
50
- display: -webkit-box;
51
- -webkit-line-clamp: 2;
52
- -webkit-box-orient: vertical;
53
- overflow: hidden;
54
- }
55
-
56
- .controls {
57
- display: flex;
58
- align-items: center;
59
- gap: 8px;
60
- flex-shrink: 0;
61
- }
62
-
63
- /* —— ad banner —— */
64
-
65
- /* 广告条:固定蓝紫渐变 + 白字,不随主题变量漂移,两种主题下都醒目。
66
- 与 toast 相同思路:固定颜色保证可读性。 */
67
- .adBanner {
68
- display: flex;
69
- align-items: center;
70
- gap: 8px;
71
- padding: 7px 12px;
72
- border: 1px solid rgba(139, 92, 246, 0.55);
73
- border-radius: 8px;
74
- background: linear-gradient(90deg, #4f46e5 0%, #7c3aed 100%);
75
- color: #ffffff;
76
- text-decoration: none;
77
- cursor: pointer;
78
- user-select: none;
79
- box-shadow: 0 2px 10px rgba(79, 70, 229, 0.28);
80
- transition: filter 0.15s ease, box-shadow 0.15s ease;
81
- }
82
-
83
- .adBanner:hover {
84
- filter: brightness(1.1);
85
- box-shadow: 0 4px 16px rgba(124, 58, 237, 0.42);
86
- }
87
-
88
- .adBadge {
89
- flex-shrink: 0;
90
- font-size: 10px;
91
- font-weight: 700;
92
- letter-spacing: 0.04em;
93
- line-height: 1;
94
- color: #ffffff;
95
- background: rgba(255, 255, 255, 0.16);
96
- border: 1px solid rgba(255, 255, 255, 0.55);
97
- border-radius: 4px;
98
- padding: 3px 6px;
99
- white-space: nowrap;
100
- }
101
-
102
- .adText {
103
- min-width: 0;
104
- overflow: hidden;
105
- text-overflow: ellipsis;
106
- white-space: nowrap;
107
- font-size: 12px;
108
- line-height: 18px;
109
- color: #ffffff;
110
- }
111
-
112
- .adArrow {
113
- flex-shrink: 0;
114
- margin-left: auto;
115
- font-size: 13px;
116
- color: #ffffff;
117
- }
118
-
119
- .search {
120
- width: 190px;
121
- font-size: 12px;
122
- line-height: 18px;
123
- color: inherit;
124
- background: transparent;
125
- border: 1px solid var(--dsw-alias-border-l2, #e5e7eb);
126
- border-radius: 6px;
127
- padding: 4px 9px;
128
- outline: none;
129
- transition: border-color 120ms ease;
130
- }
131
-
132
- .search::placeholder {
133
- color: var(--dsw-alias-label-tertiary, #8b93a1);
134
- }
135
-
136
- .search:focus {
137
- border-color: var(--dsw-alias-brand-primary, #4f6ef7);
138
- }
139
-
140
- .sort {
141
- font-size: 12px;
142
- line-height: 18px;
143
- color: var(--dsw-alias-label-secondary, #6b7280);
144
- background: transparent;
145
- border: 1px solid var(--dsw-alias-border-l2, #e5e7eb);
146
- border-radius: 6px;
147
- padding: 4px 8px;
148
- outline: none;
149
- cursor: pointer;
150
- user-select: none;
151
- }
152
-
153
- .sort:hover {
154
- color: var(--dsw-alias-label-primary, #1f2328);
155
- background: var(--dsw-alias-interactive-bg-hover, rgba(128, 128, 128, 0.08));
156
- border-color: var(--dsw-alias-button-ghost-active-border, #d0d4da);
157
- }
158
-
159
- .langBtn {
160
- flex-shrink: 0;
161
- font-size: 12px;
162
- line-height: 18px;
163
- color: var(--dsw-alias-label-secondary, #6b7280);
164
- background: transparent;
165
- border: 1px solid var(--dsw-alias-border-l2, #e5e7eb);
166
- border-radius: 6px;
167
- padding: 4px 9px;
168
- cursor: pointer;
169
- user-select: none;
170
- transition: color 120ms ease, border-color 120ms ease;
171
- }
172
-
173
- .langBtn:hover {
174
- color: var(--dsw-alias-label-primary, #1f2328);
175
- background: var(--dsw-alias-interactive-bg-hover, rgba(128, 128, 128, 0.08));
176
- border-color: var(--dsw-alias-button-ghost-active-border, #d0d4da);
177
- }
178
-
179
- .openBtn {
180
- flex-shrink: 0;
181
- display: inline-flex;
182
- align-items: center;
183
- gap: 4px;
184
- font-size: 12px;
185
- font-weight: 500;
186
- line-height: 20px;
187
- color: var(--dsw-alias-label-secondary, #6b7280);
188
- text-decoration: none;
189
- border: 1px solid var(--dsw-alias-border-l2, #e5e7eb);
190
- border-radius: 6px;
191
- padding: 3px 9px;
192
- background: transparent;
193
- cursor: pointer;
194
- white-space: nowrap;
195
- user-select: none;
196
- transition: color 120ms ease, border-color 120ms ease;
197
- }
198
-
199
- .openBtn:hover {
200
- color: var(--dsw-alias-label-primary, #1f2328);
201
- background: var(--dsw-alias-interactive-bg-hover, rgba(128, 128, 128, 0.08));
202
- border-color: var(--dsw-alias-button-ghost-active-border, #d0d4da);
203
- }
204
-
205
- .openArrow {
206
- font-size: 12px;
207
- line-height: 1;
208
- }
209
-
210
- /* —— category chips —— */
211
-
212
- .tabs {
213
- display: flex;
214
- align-items: center;
215
- flex-wrap: wrap;
216
- gap: 6px;
217
- padding: 2px 2px 8px;
218
- border-bottom: 1px solid var(--dsw-alias-border-l1, #eceef1);
219
- }
220
-
221
- .tab,
222
- .tabActive {
223
- display: inline-flex;
224
- align-items: center;
225
- gap: 6px;
226
- flex-shrink: 0;
227
- font-size: 12px;
228
- line-height: 18px;
229
- border: 1px solid transparent;
230
- border-radius: 999px;
231
- padding: 3px 10px;
232
- cursor: pointer;
233
- user-select: none;
234
- transition: color 120ms ease, background 120ms ease, border-color 120ms ease;
235
- }
236
-
237
- .tab {
238
- color: var(--dsw-alias-label-secondary, #6b7280);
239
- background: transparent;
240
- border-color: var(--dsw-alias-border-l2, #e5e7eb);
241
- }
242
-
243
- .tab:hover {
244
- color: var(--dsw-alias-label-primary, #1f2328);
245
- background: var(--dsw-alias-interactive-bg-hover, rgba(128, 128, 128, 0.08));
246
- border-color: var(--dsw-alias-border-l1, #eceef1);
247
- }
248
-
249
- .tabActive {
250
- color: var(--dsw-alias-label-primary-foreground, #fff);
251
- background: var(--dsw-alias-button-primary-fill, #1f2328);
252
- border-color: var(--dsw-alias-button-primary-fill, #1f2328);
253
- }
254
-
255
- .tabCount,
256
- .tabCountActive {
257
- font-size: 10px;
258
- line-height: 14px;
259
- border-radius: 999px;
260
- padding: 0 5px;
261
- min-width: 16px;
262
- text-align: center;
263
- }
264
-
265
- .tabCount {
266
- color: var(--dsw-alias-label-tertiary, #8b93a1);
267
- background: var(--dsw-alias-bg-layer-2, rgba(128, 128, 128, 0.1));
268
- }
269
-
270
- .tabActive .tabCountActive {
271
- color: var(--dsw-alias-label-primary-foreground, #fff);
272
- background: rgba(128, 128, 128, 0.22);
273
- }
274
-
275
- /* —— body / list —— */
276
-
277
- .body {
278
- flex: 1;
279
- min-height: 0;
280
- display: flex;
281
- flex-direction: column;
282
- }
283
-
284
- .list {
285
- flex: 1;
286
- min-height: 0;
287
- overflow-y: auto;
288
- display: flex;
289
- flex-direction: column;
290
- gap: 6px;
291
- padding: 2px 4px 4px 2px;
292
- }
293
-
294
- .card {
295
- display: flex;
296
- align-items: stretch;
297
- justify-content: space-between;
298
- gap: 12px;
299
- border: 1px solid var(--dsw-alias-border-l2, #e5e7eb);
300
- border-radius: 8px;
301
- padding: 9px 12px;
302
- background: var(--dsw-alias-bg-layer-1, rgba(128, 128, 128, 0.06));
303
- transition: border-color 120ms ease, background 120ms ease;
304
- }
305
-
306
- .card:hover {
307
- border-color: var(--dsw-alias-brand-primary, #4f6ef7);
308
- background: var(--dsw-alias-bg-layer-2, rgba(128, 128, 128, 0.1));
309
- }
310
-
311
- .cardMain {
312
- min-width: 0;
313
- display: flex;
314
- flex-direction: column;
315
- gap: 4px;
316
- }
317
-
318
- .cardHead {
319
- display: flex;
320
- align-items: center;
321
- gap: 6px;
322
- min-width: 0;
323
- }
324
-
325
- .cardTitle {
326
- font-size: 13px;
327
- line-height: 18px;
328
- font-weight: 600;
329
- white-space: nowrap;
330
- overflow: hidden;
331
- text-overflow: ellipsis;
332
- }
333
-
334
- .categoryBadge,
335
- .verified {
336
- flex-shrink: 0;
337
- font-size: 10px;
338
- line-height: 16px;
339
- border-radius: 4px;
340
- padding: 0 6px;
341
- border: 1px solid transparent;
342
- }
343
-
344
- .categoryBadge {
345
- color: var(--dsw-alias-brand-primary, #4f6ef7);
346
- border-color: var(--dsw-alias-button-ghost-active-border, rgba(79, 110, 247, 0.25));
347
- }
348
-
349
- .verified {
350
- color: var(--dsw-alias-state-success-primary, #1a7f37);
351
- border-color: var(--dsw-alias-state-success-secondary, rgba(26, 127, 55, 0.3));
352
- }
353
-
354
- .desc {
355
- font-size: 12px;
356
- line-height: 18px;
357
- color: var(--dsw-alias-label-secondary, #6b7280);
358
- margin: 0;
359
- display: -webkit-box;
360
- -webkit-line-clamp: 2;
361
- -webkit-box-orient: vertical;
362
- overflow: hidden;
363
- }
364
-
365
- .topics {
366
- display: flex;
367
- align-items: center;
368
- gap: 4px;
369
- flex-wrap: wrap;
370
- min-width: 0;
371
- }
372
-
373
- .topic {
374
- flex-shrink: 0;
375
- font-size: 10px;
376
- line-height: 16px;
377
- color: var(--dsw-alias-label-tertiary, #8b93a1);
378
- background: var(--dsw-alias-bg-layer-2, rgba(128, 128, 128, 0.1));
379
- border-radius: 4px;
380
- padding: 0 6px;
381
- white-space: nowrap;
382
- }
383
-
384
- .cardSide {
385
- flex-shrink: 0;
386
- display: flex;
387
- flex-direction: column;
388
- align-items: flex-end;
389
- justify-content: space-between;
390
- gap: 6px;
391
- }
392
-
393
- .stats {
394
- display: flex;
395
- flex-direction: column;
396
- align-items: flex-end;
397
- gap: 1px;
398
- }
399
-
400
- .star {
401
- font-size: 11px;
402
- line-height: 16px;
403
- color: var(--dsw-alias-state-warn-primary, #b8860b);
404
- white-space: nowrap;
405
- }
406
-
407
- .fork {
408
- font-size: 11px;
409
- line-height: 16px;
410
- color: var(--dsw-alias-label-tertiary, #8b93a1);
411
- white-space: nowrap;
412
- }
413
-
414
- .date {
415
- font-size: 10px;
416
- line-height: 14px;
417
- color: var(--dsw-alias-label-tertiary, #8b93a1);
418
- white-space: nowrap;
419
- }
420
-
421
- .installBtn,
422
- .installBtnCopied,
423
- .detailBtn {
424
- font-size: 11px;
425
- line-height: 18px;
426
- border-radius: 6px;
427
- padding: 2px 10px;
428
- cursor: pointer;
429
- transition: color 120ms ease, border-color 120ms ease, background 120ms ease;
430
- }
431
-
432
- .installBtn {
433
- color: var(--dsw-alias-label-primary, #1f2328);
434
- background: var(--dsw-alias-bg-layer-2, rgba(128, 128, 128, 0.06));
435
- border: 1px solid var(--dsw-alias-border-l2, #e5e7eb);
436
- user-select: none;
437
- }
438
-
439
- .installBtn:hover {
440
- color: var(--dsw-alias-label-primary, #1f2328);
441
- background: var(--dsw-alias-interactive-bg-hover, rgba(128, 128, 128, 0.12));
442
- border-color: var(--dsw-alias-button-ghost-active-border, #d0d4da);
443
- }
444
-
445
- /* Primary action: opens the plugin detail page on dsh-plugin.org.
446
- Uses the host primary-button tokens so it follows the active theme
447
- (light: dark fill / dark: light fill). */
448
- .detailBtn {
449
- color: var(--dsw-alias-label-primary-foreground, #fff);
450
- background: var(--dsw-alias-button-primary-fill, #1f2328);
451
- border: 1px solid transparent;
452
- text-decoration: none;
453
- display: inline-flex;
454
- align-items: center;
455
- font-weight: 500;
456
- user-select: none;
457
- }
458
-
459
- .detailBtn:hover {
460
- background: var(--dsw-alias-button-primary-hover, #43454a);
461
- }
462
-
463
- .actions {
464
- display: flex;
465
- align-items: center;
466
- gap: 6px;
467
- }
468
-
469
- /* 复制成功态:成功绿描边 + 绿字 + 淡绿底,避免亮色下黑色实心块、暗色下白色实心块
470
- 的强对比,两种主题下都清晰醒目,且明确表达"已复制"语义。 */
471
- .installBtnCopied {
472
- color: var(--dsw-alias-state-success-primary, #1a7f37);
473
- background: rgba(34, 197, 94, 0.14);
474
- border: 1px solid var(--dsw-alias-state-success-secondary, rgba(26, 127, 55, 0.4));
475
- user-select: none;
476
- }
477
-
478
- /* 复制成功提示条:窗口正中央浮出,无遮罩无图标。采用反色浮层——亮色模式
479
- 黑底白字、暗色模式白底深字(复用主按钮 token:primary-fill 与
480
- label-primary-foreground 在两种主题下正好互换),与页面背景相反,始终醒目。
481
- base 上直接写死居中 transform,动画结束(fill-mode 默认 none)后
482
- transform 会回落到 base 值,保证动画前后位置一致、不跳动。 */
483
- .toast {
484
- position: fixed;
485
- left: 50%;
486
- top: 50%;
487
- transform: translate(-50%, -50%);
488
- z-index: 1000;
489
- padding: 10px 16px;
490
- border-radius: 8px;
491
- background: var(--dsw-alias-button-primary-fill, #1f2328);
492
- border: 1px solid transparent;
493
- color: var(--dsw-alias-label-primary-foreground, #fff);
494
- font-size: 12px;
495
- font-weight: 500;
496
- line-height: 18px;
497
- box-shadow: 0 6px 24px rgba(0, 0, 0, 0.28);
498
- animation: toastIn 220ms ease-out;
499
- pointer-events: none;
500
- white-space: nowrap;
501
- }
502
-
503
- @keyframes toastIn {
504
- from {
505
- opacity: 0;
506
- transform: translate(-50%, -44%);
507
- }
508
- to {
509
- opacity: 1;
510
- transform: translate(-50%, -50%);
511
- }
512
- }
513
-
514
- /* —— states —— */
515
-
516
- .state {
517
- flex: 1;
518
- min-height: 160px;
519
- display: flex;
520
- flex-direction: column;
521
- align-items: center;
522
- justify-content: center;
523
- gap: 8px;
524
- padding: 24px;
525
- text-align: center;
526
- font-size: 12px;
527
- line-height: 18px;
528
- color: var(--dsw-alias-label-tertiary, #8b93a1);
529
- }
530
-
531
- .stateTitle {
532
- font-size: 13px;
533
- font-weight: 600;
534
- color: var(--dsw-alias-label-primary, #1f2328);
535
- }
536
-
537
- .stateDesc {
538
- max-width: 420px;
539
- }
540
-
541
- .retryBtn {
542
- margin-top: 4px;
543
- font-size: 12px;
544
- line-height: 18px;
545
- color: var(--dsw-alias-label-secondary, #6b7280);
546
- border: 1px solid var(--dsw-alias-border-l2, #e5e7eb);
547
- border-radius: 6px;
548
- padding: 4px 12px;
549
- background: transparent;
550
- cursor: pointer;
551
- user-select: none;
552
- }
553
-
554
- .retryBtn:hover {
555
- color: var(--dsw-alias-label-primary, #1f2328);
556
- background: var(--dsw-alias-interactive-bg-hover, rgba(128, 128, 128, 0.08));
557
- border-color: var(--dsw-alias-button-ghost-active-border, #d0d4da);
558
- }
559
-
560
- /* —— footer —— */
561
-
562
- .footer {
563
- flex-shrink: 0;
564
- display: flex;
565
- align-items: center;
566
- justify-content: space-between;
567
- gap: 8px;
568
- padding: 6px 4px 0;
569
- border-top: 1px solid var(--dsw-alias-border-l1, #eceef1);
570
- }
571
-
572
- .footText {
573
- font-size: 11px;
574
- line-height: 16px;
575
- color: var(--dsw-alias-label-tertiary, #8b93a1);
576
- }
577
-
578
- .footLink {
579
- font-size: 11px;
580
- line-height: 16px;
581
- color: var(--dsw-alias-brand-primary, #4f6ef7);
582
- text-decoration: none;
583
- white-space: nowrap;
584
- user-select: none;
585
- }
586
-
587
- .footLink:hover {
588
- text-decoration: underline;
589
- }