dsh-plugin 0.1.0 → 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 +52 -27
  2. package/README.md +52 -27
  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 -569
@@ -1,569 +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
- box-shadow: 0 2px 10px rgba(79, 70, 229, 0.28);
79
- transition: filter 0.15s ease, box-shadow 0.15s ease;
80
- }
81
-
82
- .adBanner:hover {
83
- filter: brightness(1.1);
84
- box-shadow: 0 4px 16px rgba(124, 58, 237, 0.42);
85
- }
86
-
87
- .adBadge {
88
- flex-shrink: 0;
89
- font-size: 10px;
90
- font-weight: 700;
91
- letter-spacing: 0.04em;
92
- line-height: 1;
93
- color: #ffffff;
94
- background: rgba(255, 255, 255, 0.16);
95
- border: 1px solid rgba(255, 255, 255, 0.55);
96
- border-radius: 4px;
97
- padding: 3px 6px;
98
- white-space: nowrap;
99
- }
100
-
101
- .adText {
102
- min-width: 0;
103
- overflow: hidden;
104
- text-overflow: ellipsis;
105
- white-space: nowrap;
106
- font-size: 12px;
107
- line-height: 18px;
108
- color: #ffffff;
109
- }
110
-
111
- .adArrow {
112
- flex-shrink: 0;
113
- margin-left: auto;
114
- font-size: 13px;
115
- color: #ffffff;
116
- }
117
-
118
- .search {
119
- width: 190px;
120
- font-size: 12px;
121
- line-height: 18px;
122
- color: inherit;
123
- background: transparent;
124
- border: 1px solid var(--dsw-alias-border-l2, #e5e7eb);
125
- border-radius: 6px;
126
- padding: 4px 9px;
127
- outline: none;
128
- transition: border-color 120ms ease;
129
- }
130
-
131
- .search::placeholder {
132
- color: var(--dsw-alias-label-tertiary, #8b93a1);
133
- }
134
-
135
- .search:focus {
136
- border-color: var(--dsw-alias-brand-primary, #4f6ef7);
137
- }
138
-
139
- .sort {
140
- font-size: 12px;
141
- line-height: 18px;
142
- color: var(--dsw-alias-label-secondary, #6b7280);
143
- background: transparent;
144
- border: 1px solid var(--dsw-alias-border-l2, #e5e7eb);
145
- border-radius: 6px;
146
- padding: 4px 8px;
147
- outline: none;
148
- cursor: pointer;
149
- }
150
-
151
- .sort:hover {
152
- color: var(--dsw-alias-brand-primary, #4f6ef7);
153
- border-color: var(--dsw-alias-brand-primary, #4f6ef7);
154
- }
155
-
156
- .langBtn {
157
- flex-shrink: 0;
158
- font-size: 12px;
159
- line-height: 18px;
160
- color: var(--dsw-alias-label-secondary, #6b7280);
161
- background: transparent;
162
- border: 1px solid var(--dsw-alias-border-l2, #e5e7eb);
163
- border-radius: 6px;
164
- padding: 4px 9px;
165
- cursor: pointer;
166
- transition: color 120ms ease, border-color 120ms ease;
167
- }
168
-
169
- .langBtn:hover {
170
- color: var(--dsw-alias-brand-primary, #4f6ef7);
171
- border-color: var(--dsw-alias-brand-primary, #4f6ef7);
172
- }
173
-
174
- .openBtn {
175
- flex-shrink: 0;
176
- display: inline-flex;
177
- align-items: center;
178
- gap: 4px;
179
- font-size: 12px;
180
- font-weight: 500;
181
- line-height: 20px;
182
- color: var(--dsw-alias-label-secondary, #6b7280);
183
- text-decoration: none;
184
- border: 1px solid var(--dsw-alias-border-l2, #e5e7eb);
185
- border-radius: 6px;
186
- padding: 3px 9px;
187
- background: transparent;
188
- cursor: pointer;
189
- white-space: nowrap;
190
- transition: color 120ms ease, border-color 120ms ease;
191
- }
192
-
193
- .openBtn:hover {
194
- color: var(--dsw-alias-brand-primary, #4f6ef7);
195
- border-color: var(--dsw-alias-brand-primary, #4f6ef7);
196
- }
197
-
198
- .openArrow {
199
- font-size: 12px;
200
- line-height: 1;
201
- }
202
-
203
- /* —— category chips —— */
204
-
205
- .tabs {
206
- display: flex;
207
- align-items: center;
208
- flex-wrap: wrap;
209
- gap: 6px;
210
- padding: 2px 2px 8px;
211
- border-bottom: 1px solid var(--dsw-alias-border-l1, #eceef1);
212
- }
213
-
214
- .tab,
215
- .tabActive {
216
- display: inline-flex;
217
- align-items: center;
218
- gap: 6px;
219
- flex-shrink: 0;
220
- font-size: 12px;
221
- line-height: 18px;
222
- border: 1px solid transparent;
223
- border-radius: 999px;
224
- padding: 3px 10px;
225
- cursor: pointer;
226
- transition: color 120ms ease, background 120ms ease, border-color 120ms ease;
227
- }
228
-
229
- .tab {
230
- color: var(--dsw-alias-label-secondary, #6b7280);
231
- background: transparent;
232
- border-color: var(--dsw-alias-border-l2, #e5e7eb);
233
- }
234
-
235
- .tab:hover {
236
- color: var(--dsw-alias-label-primary, #1f2328);
237
- background: var(--dsw-alias-interactive-bg-hover, rgba(128, 128, 128, 0.08));
238
- border-color: var(--dsw-alias-border-l1, #eceef1);
239
- }
240
-
241
- .tabActive {
242
- color: #fff;
243
- background: var(--dsw-alias-brand-primary, #4f6ef7);
244
- border-color: var(--dsw-alias-brand-primary, #4f6ef7);
245
- }
246
-
247
- .tabCount,
248
- .tabCountActive {
249
- font-size: 10px;
250
- line-height: 14px;
251
- border-radius: 999px;
252
- padding: 0 5px;
253
- min-width: 16px;
254
- text-align: center;
255
- }
256
-
257
- .tabCount {
258
- color: var(--dsw-alias-label-tertiary, #8b93a1);
259
- background: var(--dsw-alias-bg-layer-2, rgba(128, 128, 128, 0.1));
260
- }
261
-
262
- .tabActive .tabCountActive {
263
- color: #fff;
264
- background: rgba(255, 255, 255, 0.22);
265
- }
266
-
267
- /* —— body / list —— */
268
-
269
- .body {
270
- flex: 1;
271
- min-height: 0;
272
- display: flex;
273
- flex-direction: column;
274
- }
275
-
276
- .list {
277
- flex: 1;
278
- min-height: 0;
279
- overflow-y: auto;
280
- display: flex;
281
- flex-direction: column;
282
- gap: 6px;
283
- padding: 2px 4px 4px 2px;
284
- }
285
-
286
- .card {
287
- display: flex;
288
- align-items: stretch;
289
- justify-content: space-between;
290
- gap: 12px;
291
- border: 1px solid var(--dsw-alias-border-l2, #e5e7eb);
292
- border-radius: 8px;
293
- padding: 9px 12px;
294
- background: var(--dsw-alias-bg-layer-1, rgba(128, 128, 128, 0.06));
295
- transition: border-color 120ms ease, background 120ms ease;
296
- }
297
-
298
- .card:hover {
299
- border-color: var(--dsw-alias-brand-primary, #4f6ef7);
300
- background: var(--dsw-alias-bg-layer-2, rgba(128, 128, 128, 0.1));
301
- }
302
-
303
- .cardMain {
304
- min-width: 0;
305
- display: flex;
306
- flex-direction: column;
307
- gap: 4px;
308
- }
309
-
310
- .cardHead {
311
- display: flex;
312
- align-items: center;
313
- gap: 6px;
314
- min-width: 0;
315
- }
316
-
317
- .cardTitle {
318
- font-size: 13px;
319
- line-height: 18px;
320
- font-weight: 600;
321
- white-space: nowrap;
322
- overflow: hidden;
323
- text-overflow: ellipsis;
324
- }
325
-
326
- .categoryBadge,
327
- .verified {
328
- flex-shrink: 0;
329
- font-size: 10px;
330
- line-height: 16px;
331
- border-radius: 4px;
332
- padding: 0 6px;
333
- border: 1px solid transparent;
334
- }
335
-
336
- .categoryBadge {
337
- color: var(--dsw-alias-brand-primary, #4f6ef7);
338
- border-color: var(--dsw-alias-button-ghost-active-border, rgba(79, 110, 247, 0.25));
339
- }
340
-
341
- .verified {
342
- color: var(--dsw-alias-state-success-primary, #1a7f37);
343
- border-color: var(--dsw-alias-state-success-secondary, rgba(26, 127, 55, 0.3));
344
- }
345
-
346
- .desc {
347
- font-size: 12px;
348
- line-height: 18px;
349
- color: var(--dsw-alias-label-secondary, #6b7280);
350
- margin: 0;
351
- display: -webkit-box;
352
- -webkit-line-clamp: 2;
353
- -webkit-box-orient: vertical;
354
- overflow: hidden;
355
- }
356
-
357
- .topics {
358
- display: flex;
359
- align-items: center;
360
- gap: 4px;
361
- flex-wrap: wrap;
362
- min-width: 0;
363
- }
364
-
365
- .topic {
366
- flex-shrink: 0;
367
- font-size: 10px;
368
- line-height: 16px;
369
- color: var(--dsw-alias-label-tertiary, #8b93a1);
370
- background: var(--dsw-alias-bg-layer-2, rgba(128, 128, 128, 0.1));
371
- border-radius: 4px;
372
- padding: 0 6px;
373
- white-space: nowrap;
374
- }
375
-
376
- .cardSide {
377
- flex-shrink: 0;
378
- display: flex;
379
- flex-direction: column;
380
- align-items: flex-end;
381
- justify-content: space-between;
382
- gap: 6px;
383
- }
384
-
385
- .stats {
386
- display: flex;
387
- flex-direction: column;
388
- align-items: flex-end;
389
- gap: 1px;
390
- }
391
-
392
- .star {
393
- font-size: 11px;
394
- line-height: 16px;
395
- color: var(--dsw-alias-state-warn-primary, #b8860b);
396
- white-space: nowrap;
397
- }
398
-
399
- .fork {
400
- font-size: 11px;
401
- line-height: 16px;
402
- color: var(--dsw-alias-label-tertiary, #8b93a1);
403
- white-space: nowrap;
404
- }
405
-
406
- .date {
407
- font-size: 10px;
408
- line-height: 14px;
409
- color: var(--dsw-alias-label-tertiary, #8b93a1);
410
- white-space: nowrap;
411
- }
412
-
413
- .installBtn,
414
- .installBtnCopied,
415
- .detailBtn {
416
- font-size: 11px;
417
- line-height: 18px;
418
- border-radius: 6px;
419
- padding: 2px 10px;
420
- cursor: pointer;
421
- transition: color 120ms ease, border-color 120ms ease, background 120ms ease;
422
- }
423
-
424
- .installBtn {
425
- color: var(--dsw-alias-label-secondary, #6b7280);
426
- background: transparent;
427
- border: 1px solid var(--dsw-alias-border-l2, #e5e7eb);
428
- }
429
-
430
- .installBtn:hover {
431
- color: #4f6ef7;
432
- border-color: #4f6ef7;
433
- }
434
-
435
- /* Primary action: opens the plugin detail page on dsh-plugin.org.
436
- Fixed colors (not host tokens) so it stays readable in dark themes. */
437
- .detailBtn {
438
- color: #4f6ef7;
439
- background: transparent;
440
- border: 1px solid #4f6ef7;
441
- text-decoration: none;
442
- display: inline-flex;
443
- align-items: center;
444
- font-weight: 500;
445
- }
446
-
447
- .detailBtn:hover {
448
- color: #ffffff;
449
- background: #4f6ef7;
450
- }
451
-
452
- .actions {
453
- display: flex;
454
- align-items: center;
455
- gap: 6px;
456
- }
457
-
458
- .installBtnCopied {
459
- color: #ffffff;
460
- background: #4f6ef7;
461
- border: 1px solid #4f6ef7;
462
- }
463
-
464
- /* 复制成功提示条:窗口正中央浮出,纯黑色文字条,无遮罩无图标。
465
- base 上直接写死居中 transform,动画结束(fill-mode 默认 none)后
466
- transform 会回落到 base 值,保证动画前后位置一致、不跳动。 */
467
- .toast {
468
- position: fixed;
469
- left: 50%;
470
- top: 50%;
471
- transform: translate(-50%, -50%);
472
- z-index: 1000;
473
- padding: 10px 16px;
474
- border-radius: 8px;
475
- background: #111827;
476
- border: 1px solid rgba(255, 255, 255, 0.1);
477
- color: #f9fafb;
478
- font-size: 12px;
479
- line-height: 18px;
480
- box-shadow: 0 6px 24px rgba(0, 0, 0, 0.22);
481
- animation: toastIn 220ms ease-out;
482
- pointer-events: none;
483
- white-space: nowrap;
484
- }
485
-
486
- @keyframes toastIn {
487
- from {
488
- opacity: 0;
489
- transform: translate(-50%, -44%);
490
- }
491
- to {
492
- opacity: 1;
493
- transform: translate(-50%, -50%);
494
- }
495
- }
496
-
497
- /* —— states —— */
498
-
499
- .state {
500
- flex: 1;
501
- min-height: 160px;
502
- display: flex;
503
- flex-direction: column;
504
- align-items: center;
505
- justify-content: center;
506
- gap: 8px;
507
- padding: 24px;
508
- text-align: center;
509
- font-size: 12px;
510
- line-height: 18px;
511
- color: var(--dsw-alias-label-tertiary, #8b93a1);
512
- }
513
-
514
- .stateTitle {
515
- font-size: 13px;
516
- font-weight: 600;
517
- color: var(--dsw-alias-label-primary, #1f2328);
518
- }
519
-
520
- .stateDesc {
521
- max-width: 420px;
522
- }
523
-
524
- .retryBtn {
525
- margin-top: 4px;
526
- font-size: 12px;
527
- line-height: 18px;
528
- color: var(--dsw-alias-label-secondary, #6b7280);
529
- border: 1px solid var(--dsw-alias-border-l2, #e5e7eb);
530
- border-radius: 6px;
531
- padding: 4px 12px;
532
- background: transparent;
533
- cursor: pointer;
534
- }
535
-
536
- .retryBtn:hover {
537
- color: var(--dsw-alias-brand-primary, #4f6ef7);
538
- border-color: var(--dsw-alias-brand-primary, #4f6ef7);
539
- }
540
-
541
- /* —— footer —— */
542
-
543
- .footer {
544
- flex-shrink: 0;
545
- display: flex;
546
- align-items: center;
547
- justify-content: space-between;
548
- gap: 8px;
549
- padding: 6px 4px 0;
550
- border-top: 1px solid var(--dsw-alias-border-l1, #eceef1);
551
- }
552
-
553
- .footText {
554
- font-size: 11px;
555
- line-height: 16px;
556
- color: var(--dsw-alias-label-tertiary, #8b93a1);
557
- }
558
-
559
- .footLink {
560
- font-size: 11px;
561
- line-height: 16px;
562
- color: var(--dsw-alias-brand-primary, #4f6ef7);
563
- text-decoration: none;
564
- white-space: nowrap;
565
- }
566
-
567
- .footLink:hover {
568
- text-decoration: underline;
569
- }