@wangnanshan/dsh-nanshan-publish-manager 1.0.4 → 1.0.6
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 +5 -3
- package/client.js +169 -5
- package/icon.svg +1 -1
- package/index.js +75 -1
- package/locale/en.json +2 -2
- package/locale/zh.json +2 -2
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
# 【南山】DSH
|
|
1
|
+
# 【南山】DSH发布Npm助手
|
|
2
2
|
|
|
3
3
|
把本机开发的 DSH 组件,按 DeepSeek Harness 官方推荐的 npm 包形式发布给其他人安装。
|
|
4
4
|
|
|
5
5
|
- **一个组件清单页**:卡片形式展示本机所有可发布的 DSH 组件(`dsh.bundle` / `dsh.client`),包含版本、发布状态、预检查结果和最近一次发布。
|
|
6
|
-
- **一个组件详情页**:点击卡片进入,用「流程化板块」完成发布——版本与标签 → 发布目标与选项 → 发布预检查 →
|
|
6
|
+
- **一个组件详情页**:点击卡片进入,用「流程化板块」完成发布——版本与标签 → 发布目标与选项 → 发布预检查 → 确认与执行,右侧常驻该组件的 **NPM信息查询**、**发布记录列表**。
|
|
7
7
|
|
|
8
8
|
界面采用现代极简的开发者工具控制台风格(Linear / Vercel / Raycast 一类的浅色克制审美),跟随 Harness 的浅色 / 深色主题。插件图标、侧栏入口和页面标识使用 3D 液态玻璃黑色调;搜索、勾选、关闭等功能图标保持线性,以保证小尺寸下的可读性。
|
|
9
9
|
|
|
@@ -86,6 +86,7 @@ DSH 组件就是一个 npm 包:
|
|
|
86
86
|
3. **发布预检查** —— 运行发布预检查;有阻断项时正式发布按钮不可用。
|
|
87
87
|
4. **确认与执行** —— 选择发布验证方式,勾选确认,先「仅演练」(`npm publish --dry-run`,临时写入的目标版本会在演练后还原)或「正式发布」(带最终确认弹窗);本机会话方式的最终弹窗按钮为:左侧「再想想」,右侧「采用终端发布(推荐)」和「采用 OTP 发布」。
|
|
88
88
|
5. **发布记录** —— 每次演练 / 发布 / 失败都会写入本机记录,包含版本、tag、access、验证方式、耗时、预检查摘要和命令输出;终端发布会在记录里标记「终端」。
|
|
89
|
+
6. **NPM信息查询** —— 右侧栏显示当前包名、近 7 天每日下载量柱状图(数据来自 npm downloads API)和「NPM查看该组件」入口。
|
|
89
90
|
|
|
90
91
|
## 发布验证方式
|
|
91
92
|
|
|
@@ -157,7 +158,7 @@ OTP 不是插件强制每次都要填写的字段:
|
|
|
157
158
|
|
|
158
159
|
## 安装
|
|
159
160
|
|
|
160
|
-
在 Harness 的插件页安装这个 bundle 目录(推荐使用 Harness 的安装入口),安装后左侧栏会出现「【南山】DSH
|
|
161
|
+
在 Harness 的插件页安装这个 bundle 目录(推荐使用 Harness 的安装入口),安装后左侧栏会出现「【南山】DSH发布Npm助手」图标。
|
|
161
162
|
|
|
162
163
|
开发时也可以在 profile 里链接:
|
|
163
164
|
|
|
@@ -225,5 +226,6 @@ HTTP 接口(仅本机、同源写入):
|
|
|
225
226
|
- 发布使用本机 npm 配置,不支持在页面里输入 token;OTP 只用于单次发布,不落盘。
|
|
226
227
|
- 系统终端发布支持 macOS Terminal、Windows `cmd.exe` 和 Linux 常见终端;Windows 的 `npm.cmd` 已做启动适配,但当前环境主要在 macOS 验证。
|
|
227
228
|
- 终端发布最多等待 15 分钟;关闭终端或未完成验证会超时,不会写入成功记录;终端输出不写入插件记录。
|
|
229
|
+
- 近 7 天下载量来自 npm downloads API,统计通常滞后约 1 天;新包或未发布包可能暂无数据。
|
|
228
230
|
- 页面直接执行 `npm publish`,请在发布前用「仅演练」确认打包内容。
|
|
229
231
|
- `npm publish` 会执行组件 `package.json` 中的 publish 生命周期脚本;请只发布你信任的组件。
|
package/client.js
CHANGED
|
@@ -12,8 +12,8 @@ window.__ModuleLoader__.load({
|
|
|
12
12
|
|
|
13
13
|
const DICTIONARIES = {
|
|
14
14
|
zh: {
|
|
15
|
-
'nav.title': '【南山】DSH
|
|
16
|
-
'page.title': '【南山】DSH
|
|
15
|
+
'nav.title': '【南山】DSH发布Npm助手',
|
|
16
|
+
'page.title': '【南山】DSH发布Npm助手',
|
|
17
17
|
'page.subtitle': '把本机 DSH 组件按官方 npm 形式发布:配置、预检查、发布与记录。',
|
|
18
18
|
'page.footnote': '官方发布形式:npm 包 + cordis.patch.yml(dsh.bundle)+ dsh.client。发布后,其他人可以在 DSH 插件页安装这个包名,组件就能直接使用。',
|
|
19
19
|
'page.npmMissing': '没有找到 npm:请先安装 Node.js / npm,再刷新本页。',
|
|
@@ -66,6 +66,15 @@ window.__ModuleLoader__.load({
|
|
|
66
66
|
'badge.host': 'Host',
|
|
67
67
|
'badge.bundlePatch': '含 patch',
|
|
68
68
|
'detail.records': '发布记录',
|
|
69
|
+
'npmInfo.title': 'NPM信息查询',
|
|
70
|
+
'npmInfo.open': 'NPM查看该组件',
|
|
71
|
+
'npmInfo.hint': '在 npm 网站查看版本、README 与安装信息;未发布时可能显示 404。',
|
|
72
|
+
'npmInfo.last7': '近 7 天下载',
|
|
73
|
+
'npmInfo.chartLabel': '近 7 天每日下载量柱形图',
|
|
74
|
+
'npmInfo.loading': '查询中…',
|
|
75
|
+
'npmInfo.empty': '暂无下载数据(新包或未发布)',
|
|
76
|
+
'npmInfo.unavailable': '下载数据暂时不可用',
|
|
77
|
+
'npmInfo.source': '数据来源:npm downloads API',
|
|
69
78
|
'detail.synopsis': '发布状态',
|
|
70
79
|
'detail.noRecords': '还没有发布记录',
|
|
71
80
|
'detail.noRecordsHint': '完成第一次演练或正式发布后,记录会按时间出现在这里。',
|
|
@@ -339,8 +348,8 @@ window.__ModuleLoader__.load({
|
|
|
339
348
|
'checkDetail.emptyList': 'npm pack 没有返回文件列表',
|
|
340
349
|
},
|
|
341
350
|
en: {
|
|
342
|
-
'nav.title': 'Nanshan DSH
|
|
343
|
-
'page.title': 'Nanshan DSH publish
|
|
351
|
+
'nav.title': 'Nanshan DSH npm publish assistant',
|
|
352
|
+
'page.title': 'Nanshan DSH npm publish assistant',
|
|
344
353
|
'page.subtitle': 'Publish local DSH components the official npm way: configure, pre-check, publish, and record.',
|
|
345
354
|
'page.footnote': 'Official bundle form: an npm package with cordis.patch.yml (dsh.bundle) and dsh.client. Once published, anyone can install the package from the DSH plugin page.',
|
|
346
355
|
'page.npmMissing': 'npm was not found. Install Node.js / npm, then refresh this page.',
|
|
@@ -393,6 +402,15 @@ window.__ModuleLoader__.load({
|
|
|
393
402
|
'badge.host': 'Host',
|
|
394
403
|
'badge.bundlePatch': 'patch',
|
|
395
404
|
'detail.records': 'Publish history',
|
|
405
|
+
'npmInfo.title': 'npm information',
|
|
406
|
+
'npmInfo.open': 'View this component on npm',
|
|
407
|
+
'npmInfo.hint': 'View versions, README and install info on npm; a 404 means it is not published yet.',
|
|
408
|
+
'npmInfo.last7': 'Downloads · last 7 days',
|
|
409
|
+
'npmInfo.chartLabel': 'Daily downloads over the last 7 days',
|
|
410
|
+
'npmInfo.loading': 'Loading…',
|
|
411
|
+
'npmInfo.empty': 'No download data yet (new or unpublished package)',
|
|
412
|
+
'npmInfo.unavailable': 'Download data is temporarily unavailable',
|
|
413
|
+
'npmInfo.source': 'Source: npm downloads API',
|
|
396
414
|
'detail.synopsis': 'Release status',
|
|
397
415
|
'detail.noRecords': 'No publish records yet',
|
|
398
416
|
'detail.noRecordsHint': 'After the first dry run or publish, entries appear here in order.',
|
|
@@ -697,7 +715,7 @@ window.__ModuleLoader__.load({
|
|
|
697
715
|
.np__title{margin:0;font-size:23px;font-weight:600;line-height:1.25;letter-spacing:-.02em}
|
|
698
716
|
.np__subtitle{margin:4px 0 0;max-width:680px;font-size:13px;line-height:1.6;color:var(--np-ink-soft)}
|
|
699
717
|
.np__actions{display:flex;align-items:center;gap:8px;flex-wrap:wrap}
|
|
700
|
-
.np-btn{appearance:none;display:inline-flex;align-items:center;justify-content:center;gap:6px;height:32px;padding:0 12px;border:1px solid var(--np-line-strong);border-radius:8px;background:var(--np-sheet);color:var(--np-ink);font:inherit;font-size:13px;font-weight:500;line-height:1;cursor:pointer;box-shadow:var(--np-shadow);transition:background-color .14s ease,border-color .14s ease,color .14s ease,opacity .14s ease;white-space:nowrap}
|
|
718
|
+
.np-btn{appearance:none;display:inline-flex;text-decoration:none;align-items:center;justify-content:center;gap:6px;height:32px;padding:0 12px;border:1px solid var(--np-line-strong);border-radius:8px;background:var(--np-sheet);color:var(--np-ink);font:inherit;font-size:13px;font-weight:500;line-height:1;cursor:pointer;box-shadow:var(--np-shadow);transition:background-color .14s ease,border-color .14s ease,color .14s ease,opacity .14s ease;white-space:nowrap}
|
|
701
719
|
.np-btn:hover:not(:disabled){background:var(--np-sheet2);border-color:var(--np-line-strong)}
|
|
702
720
|
.np-btn:active:not(:disabled){background:var(--np-sheet2)}
|
|
703
721
|
.np-btn:disabled{opacity:.45;cursor:not-allowed;box-shadow:none}
|
|
@@ -856,7 +874,24 @@ window.__ModuleLoader__.load({
|
|
|
856
874
|
.np-result__close:focus-visible{outline:2px solid var(--np-bad);outline-offset:2px}
|
|
857
875
|
.np-result__cmd{padding:7px 11px;border-bottom:1px solid var(--np-line);font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:11.5px;line-height:1.55;color:var(--np-ink-soft);word-break:break-all}
|
|
858
876
|
.np-log{margin:0;padding:10px 12px;max-height:260px;overflow:auto;white-space:pre-wrap;word-break:break-word;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:11.5px;line-height:1.55;color:var(--np-ink-soft)}
|
|
877
|
+
.np-downloads{display:flex;flex-direction:column;gap:8px;padding-top:2px}
|
|
878
|
+
.np-downloads__head{display:flex;align-items:baseline;justify-content:space-between;gap:8px;font-size:11.5px;color:var(--np-ink-soft)}
|
|
879
|
+
.np-downloads__head strong{font-size:14px;font-weight:600;color:var(--np-ink);font-variant-numeric:tabular-nums}
|
|
880
|
+
.np-downloads__loading{font-size:11px;color:var(--np-ink-soft)}
|
|
881
|
+
.np-bars{display:grid;grid-template-columns:repeat(7,minmax(0,1fr));align-items:end;gap:5px;height:96px;padding-top:14px}
|
|
882
|
+
.np-bar{display:flex;flex-direction:column;align-items:center;justify-content:flex-end;gap:3px;min-width:0}
|
|
883
|
+
.np-bar__value{font-size:9px;line-height:1;color:var(--np-ink-soft);font-variant-numeric:tabular-nums;white-space:nowrap}
|
|
884
|
+
.np-bar__track{display:flex;align-items:flex-end;width:100%;max-width:18px;height:62px;overflow:hidden;border-radius:3px;background:var(--np-sheet2)}
|
|
885
|
+
.np-bar__fill{display:block;width:100%;min-height:2px;border-radius:3px 3px 0 0;background:var(--np-accent);opacity:.9}
|
|
886
|
+
.np-bar__label{font-size:9px;line-height:1;color:var(--np-ink-soft);white-space:nowrap}
|
|
887
|
+
.np-downloads__hint{font-size:10px;line-height:1.5;color:var(--np-ink-soft)}
|
|
888
|
+
.np-bars--skeleton{height:96px;padding-top:14px}
|
|
889
|
+
.np-bar__skeleton{display:block;width:100%;max-width:18px;height:62px;align-self:end;justify-self:center;border-radius:3px;background:linear-gradient(100deg,var(--np-sheet2) 20%,var(--np-line) 45%,var(--np-sheet2) 70%);background-size:200% 100%;animation:np-shimmer 1.5s linear infinite}
|
|
859
890
|
.np-railcard{position:relative;padding:16px;background:var(--np-sheet);border:1px solid var(--np-line);border-radius:12px;box-shadow:var(--np-shadow)}
|
|
891
|
+
.np-railcard__body{display:flex;flex-direction:column;gap:8px}
|
|
892
|
+
.np-railcard__pkg{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:11.5px;color:var(--np-ink-soft);word-break:break-all}
|
|
893
|
+
.np-railcard__hint{font-size:11px;line-height:1.55;color:var(--np-ink-soft)}
|
|
894
|
+
.np-railcard__action{width:100%;justify-content:center}
|
|
860
895
|
.np-railcard__title{display:flex;align-items:center;justify-content:space-between;gap:8px;margin:0 0 12px;font-size:13px;font-weight:600;line-height:1.3}
|
|
861
896
|
.np-dl{display:grid;grid-template-columns:auto minmax(0,1fr);gap:8px 12px;margin:0;font-size:12px;line-height:1.5}
|
|
862
897
|
.np-dl dt{color:var(--np-ink-soft)}
|
|
@@ -977,6 +1012,19 @@ window.__ModuleLoader__.load({
|
|
|
977
1012
|
return text
|
|
978
1013
|
}
|
|
979
1014
|
|
|
1015
|
+
function formatCount(value) {
|
|
1016
|
+
const n = Number(value) || 0
|
|
1017
|
+
if (n >= 1_000_000) return `${(n / 1_000_000).toFixed(n >= 10_000_000 ? 0 : 1)}M`
|
|
1018
|
+
if (n >= 10_000) return `${Math.round(n / 1000)}k`
|
|
1019
|
+
if (n >= 1000) return `${(n / 1000).toFixed(1)}k`
|
|
1020
|
+
return String(n)
|
|
1021
|
+
}
|
|
1022
|
+
|
|
1023
|
+
function shortDay(value) {
|
|
1024
|
+
const text = String(value || '')
|
|
1025
|
+
return text.length >= 10 ? text.slice(5, 10) : text
|
|
1026
|
+
}
|
|
1027
|
+
|
|
980
1028
|
function formatDateTime(value) {
|
|
981
1029
|
const date = new Date(value)
|
|
982
1030
|
if (Number.isNaN(date.getTime())) return '—'
|
|
@@ -1260,6 +1308,10 @@ window.__ModuleLoader__.load({
|
|
|
1260
1308
|
['rect', { x: 2.2, y: 3.2, width: 11.6, height: 9.6, rx: 1.6 }],
|
|
1261
1309
|
['path', { d: 'm4.8 6.1 2.4 2.2-2.4 2.2M8.6 10.5h2.8' }],
|
|
1262
1310
|
],
|
|
1311
|
+
external: [
|
|
1312
|
+
['path', { d: 'M9.4 2.8H13v3.6M13 2.8 8.2 7.6' }],
|
|
1313
|
+
['path', { d: 'M12 9.2v2.2a1.6 1.6 0 0 1-1.6 1.6H3.8A1.6 1.6 0 0 1 2.2 11.4V4.8a1.6 1.6 0 0 1 1.6-1.6h2.4' }],
|
|
1314
|
+
],
|
|
1263
1315
|
pencil: [
|
|
1264
1316
|
['path', { d: 'M3 13l.9-3.1 6.3-6.3 2.2 2.2-6.3 6.3L3 13Z' }],
|
|
1265
1317
|
['path', { d: 'M9.6 4.2 11.8 6.4' }],
|
|
@@ -1375,6 +1427,69 @@ window.__ModuleLoader__.load({
|
|
|
1375
1427
|
)
|
|
1376
1428
|
}
|
|
1377
1429
|
|
|
1430
|
+
function DownloadBars(props) {
|
|
1431
|
+
const [t] = useText()
|
|
1432
|
+
const status = props.status || 'loading'
|
|
1433
|
+
const points = Array.isArray(props.points) ? props.points : []
|
|
1434
|
+
const total = Number(props.total) || 0
|
|
1435
|
+
if (status === 'loading') {
|
|
1436
|
+
return h('div', { className: 'np-downloads' },
|
|
1437
|
+
h('div', { className: 'np-downloads__head' },
|
|
1438
|
+
h('span', null, t('npmInfo.last7')),
|
|
1439
|
+
h('span', { className: 'np-downloads__loading' }, t('npmInfo.loading')),
|
|
1440
|
+
),
|
|
1441
|
+
h('div', { className: 'np-bars np-bars--skeleton', 'aria-hidden': 'true' },
|
|
1442
|
+
Array.from({ length: 7 }, (_, index) => h('span', { key: index, className: 'np-bar__skeleton' })),
|
|
1443
|
+
),
|
|
1444
|
+
)
|
|
1445
|
+
}
|
|
1446
|
+
if (status === 'unavailable' || status === 'error') {
|
|
1447
|
+
return h('div', { className: 'np-downloads' },
|
|
1448
|
+
h('div', { className: 'np-downloads__head' }, h('span', null, t('npmInfo.last7'))),
|
|
1449
|
+
h('span', { className: 'np-downloads__hint', title: props.error || undefined }, t('npmInfo.unavailable')),
|
|
1450
|
+
)
|
|
1451
|
+
}
|
|
1452
|
+
if (status === 'empty' || points.length === 0) {
|
|
1453
|
+
return h('div', { className: 'np-downloads' },
|
|
1454
|
+
h('div', { className: 'np-downloads__head' },
|
|
1455
|
+
h('span', null, t('npmInfo.last7')),
|
|
1456
|
+
h('strong', null, '0'),
|
|
1457
|
+
),
|
|
1458
|
+
h('span', { className: 'np-downloads__hint' }, t('npmInfo.empty')),
|
|
1459
|
+
)
|
|
1460
|
+
}
|
|
1461
|
+
const max = Math.max(1, ...points.map((point) => Number(point.downloads) || 0))
|
|
1462
|
+
return h('div', { className: 'np-downloads' },
|
|
1463
|
+
h('div', { className: 'np-downloads__head' },
|
|
1464
|
+
h('span', null, t('npmInfo.last7')),
|
|
1465
|
+
h('strong', null, formatCount(total)),
|
|
1466
|
+
),
|
|
1467
|
+
h('div', {
|
|
1468
|
+
className: 'np-bars',
|
|
1469
|
+
role: 'img',
|
|
1470
|
+
'aria-label': t('npmInfo.chartLabel'),
|
|
1471
|
+
style: { gridTemplateColumns: `repeat(${Math.max(points.length, 1)},minmax(0,1fr))` },
|
|
1472
|
+
},
|
|
1473
|
+
points.map((point, index) => {
|
|
1474
|
+
const value = Number(point.downloads) || 0
|
|
1475
|
+
const height = Math.max(2, Math.round((value / max) * 100))
|
|
1476
|
+
return h('div', {
|
|
1477
|
+
key: point.day || index,
|
|
1478
|
+
className: 'np-bar',
|
|
1479
|
+
title: `${point.day}: ${formatCount(value)}`,
|
|
1480
|
+
},
|
|
1481
|
+
h('span', { className: 'np-bar__value' }, value > 0 ? formatCount(value) : ''),
|
|
1482
|
+
h('span', { className: 'np-bar__track' },
|
|
1483
|
+
h('span', { className: 'np-bar__fill', style: { height: `${height}%` } }),
|
|
1484
|
+
),
|
|
1485
|
+
h('span', { className: 'np-bar__label' }, shortDay(point.day)),
|
|
1486
|
+
)
|
|
1487
|
+
}),
|
|
1488
|
+
),
|
|
1489
|
+
h('span', { className: 'np-downloads__hint' }, t('npmInfo.source')),
|
|
1490
|
+
)
|
|
1491
|
+
}
|
|
1492
|
+
|
|
1378
1493
|
function Toast(props) {
|
|
1379
1494
|
if (!props.toast) return null
|
|
1380
1495
|
return h('div', { className: `np-toast np-toast--${props.toast.kind}`, role: 'status', 'aria-live': 'polite' },
|
|
@@ -1665,6 +1780,7 @@ window.__ModuleLoader__.load({
|
|
|
1665
1780
|
const [showToken, setShowToken] = React.useState(false)
|
|
1666
1781
|
const [toast, showToast] = useToast()
|
|
1667
1782
|
const [errorDialog, setErrorDialog] = React.useState(null)
|
|
1783
|
+
const [downloads, setDownloads] = React.useState({ status: 'loading', points: [], total: 0, error: '' })
|
|
1668
1784
|
const okButtonRef = React.useRef(null)
|
|
1669
1785
|
const otpInputRef = React.useRef(null)
|
|
1670
1786
|
const tokenInputRef = React.useRef(null)
|
|
@@ -1753,6 +1869,32 @@ window.__ModuleLoader__.load({
|
|
|
1753
1869
|
const effectiveAccess = isScopedPackage ? (settings ? settings.access : 'public') : 'public'
|
|
1754
1870
|
const isPrerelease = typeof targetVersion === 'string' && targetVersion.includes('-')
|
|
1755
1871
|
|
|
1872
|
+
React.useEffect(() => {
|
|
1873
|
+
if (!packageName) return undefined
|
|
1874
|
+
const controller = new AbortController()
|
|
1875
|
+
setDownloads({ status: 'loading', points: [], total: 0, error: '' })
|
|
1876
|
+
apiGet(`/downloads?dir=${encodeURIComponent(dir)}`, controller.signal)
|
|
1877
|
+
.then((data) => {
|
|
1878
|
+
const points = Array.isArray(data.points) ? data.points : []
|
|
1879
|
+
setDownloads({
|
|
1880
|
+
status: data.unavailable ? 'unavailable' : points.length > 0 ? 'ready' : 'empty',
|
|
1881
|
+
points,
|
|
1882
|
+
total: Number(data.total) || 0,
|
|
1883
|
+
error: data.error || '',
|
|
1884
|
+
})
|
|
1885
|
+
})
|
|
1886
|
+
.catch((failure) => {
|
|
1887
|
+
if (failure && failure.name === 'AbortError') return
|
|
1888
|
+
setDownloads({
|
|
1889
|
+
status: 'error',
|
|
1890
|
+
points: [],
|
|
1891
|
+
total: 0,
|
|
1892
|
+
error: failure && failure.message ? failure.message : '',
|
|
1893
|
+
})
|
|
1894
|
+
})
|
|
1895
|
+
return () => controller.abort()
|
|
1896
|
+
}, [dir, packageName])
|
|
1897
|
+
|
|
1756
1898
|
const closeError = React.useCallback(() => setErrorDialog(null), [])
|
|
1757
1899
|
const showError = React.useCallback((title, message, detail, hint) => {
|
|
1758
1900
|
setModalOpen(false)
|
|
@@ -2025,6 +2167,7 @@ window.__ModuleLoader__.load({
|
|
|
2025
2167
|
if (!component || !settings) return null
|
|
2026
2168
|
|
|
2027
2169
|
const recordSummary = component.lastPublish || null
|
|
2170
|
+
const npmUrl = packageName ? `https://www.npmjs.com/package/${packageName}` : ''
|
|
2028
2171
|
|
|
2029
2172
|
return h('div', { className: 'np__inner' },
|
|
2030
2173
|
h('div', { className: 'np-detail__head' },
|
|
@@ -2419,6 +2562,27 @@ window.__ModuleLoader__.load({
|
|
|
2419
2562
|
h('dd', null, npmInfo ? (npmInfo.username ? tr('synopsis.npmUser', { name: npmInfo.username }) : t('synopsis.npmUnknown')) : t('synopsis.npmIdle')),
|
|
2420
2563
|
),
|
|
2421
2564
|
),
|
|
2565
|
+
h('section', { className: 'np-railcard' },
|
|
2566
|
+
h('h2', { className: 'np-railcard__title' }, t('npmInfo.title')),
|
|
2567
|
+
h('div', { className: 'np-railcard__body' },
|
|
2568
|
+
h('code', { className: 'np-railcard__pkg' }, packageName || '—'),
|
|
2569
|
+
h('span', { className: 'np-railcard__hint' }, t('npmInfo.hint')),
|
|
2570
|
+
h(DownloadBars, {
|
|
2571
|
+
status: downloads.status,
|
|
2572
|
+
points: downloads.points,
|
|
2573
|
+
total: downloads.total,
|
|
2574
|
+
error: downloads.error,
|
|
2575
|
+
}),
|
|
2576
|
+
npmUrl
|
|
2577
|
+
? h('a', {
|
|
2578
|
+
className: 'np-btn np-btn--small np-railcard__action',
|
|
2579
|
+
href: npmUrl,
|
|
2580
|
+
target: '_blank',
|
|
2581
|
+
rel: 'noreferrer noopener',
|
|
2582
|
+
}, h(Glyph, { name: 'external', size: 13 }), t('npmInfo.open'))
|
|
2583
|
+
: h('button', { type: 'button', className: 'np-btn np-btn--small np-railcard__action', disabled: true }, h(Glyph, { name: 'external', size: 13 }), t('npmInfo.open')),
|
|
2584
|
+
),
|
|
2585
|
+
),
|
|
2422
2586
|
h('section', { className: 'np-railcard' },
|
|
2423
2587
|
h('h2', { className: 'np-railcard__title' },
|
|
2424
2588
|
t('detail.records'),
|
package/icon.svg
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128" width="128" height="128" role="img" aria-label="Nanshan DSH publish
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128" width="128" height="128" role="img" aria-label="Nanshan DSH npm publish assistant">
|
|
2
2
|
<defs>
|
|
3
3
|
<linearGradient id="np-bg" x1="18" y1="10" x2="110" y2="120" gradientUnits="userSpaceOnUse">
|
|
4
4
|
<stop offset="0" stop-color="#262932"/>
|
package/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Host half of 【南山】DSH
|
|
2
|
+
* Host half of 【南山】DSH发布Npm助手.
|
|
3
3
|
*
|
|
4
4
|
* DSH components ship as ordinary npm packages: a bundle declares
|
|
5
5
|
* `dsh.bundle.patch` (a cordis.patch.yml row) and, when it draws UI, a
|
|
@@ -84,6 +84,7 @@ export function apply(ctx, config = {}) {
|
|
|
84
84
|
let discoveryCache = { at: 0, value: null }
|
|
85
85
|
let overviewCache = { at: 0, value: null }
|
|
86
86
|
let npmCache = { at: 0, value: null }
|
|
87
|
+
const downloadsCache = new Map()
|
|
87
88
|
let npmrcRegistry = null
|
|
88
89
|
const publishing = new Set()
|
|
89
90
|
|
|
@@ -1725,6 +1726,76 @@ export function apply(ctx, config = {}) {
|
|
|
1725
1726
|
}
|
|
1726
1727
|
}
|
|
1727
1728
|
|
|
1729
|
+
async function getDownloads(rawDir) {
|
|
1730
|
+
const { pkg } = await requireComponentDir(rawDir)
|
|
1731
|
+
const name = typeof pkg.name === 'string' ? pkg.name : ''
|
|
1732
|
+
if (!name) throw httpError(400, '该组件没有可查询的 npm 包名。')
|
|
1733
|
+
const cached = downloadsCache.get(name)
|
|
1734
|
+
if (cached && Date.now() - cached.at < cached.ttl) return cached.value
|
|
1735
|
+
const remember = (value, ttl) => {
|
|
1736
|
+
downloadsCache.set(name, { at: Date.now(), ttl, value })
|
|
1737
|
+
return value
|
|
1738
|
+
}
|
|
1739
|
+
const unavailable = (message) => remember({
|
|
1740
|
+
package: name,
|
|
1741
|
+
points: [],
|
|
1742
|
+
total: 0,
|
|
1743
|
+
start: null,
|
|
1744
|
+
end: null,
|
|
1745
|
+
unavailable: true,
|
|
1746
|
+
error: message,
|
|
1747
|
+
fetchedAt: new Date().toISOString(),
|
|
1748
|
+
}, 2 * 60_000)
|
|
1749
|
+
const controller = new AbortController()
|
|
1750
|
+
const timer = setTimeout(() => controller.abort(), 10_000)
|
|
1751
|
+
let response
|
|
1752
|
+
try {
|
|
1753
|
+
response = await fetch(`https://api.npmjs.org/downloads/range/last-week/${encodeURIComponent(name)}`, {
|
|
1754
|
+
signal: controller.signal,
|
|
1755
|
+
headers: { accept: 'application/json' },
|
|
1756
|
+
})
|
|
1757
|
+
} catch (error) {
|
|
1758
|
+
return unavailable(error && error.name === 'AbortError' ? '查询下载量超时。' : '无法连接 npm 下载统计接口。')
|
|
1759
|
+
} finally {
|
|
1760
|
+
clearTimeout(timer)
|
|
1761
|
+
}
|
|
1762
|
+
if (response.status === 404) {
|
|
1763
|
+
return remember({
|
|
1764
|
+
package: name,
|
|
1765
|
+
points: [],
|
|
1766
|
+
total: 0,
|
|
1767
|
+
start: null,
|
|
1768
|
+
end: null,
|
|
1769
|
+
unavailable: false,
|
|
1770
|
+
empty: true,
|
|
1771
|
+
fetchedAt: new Date().toISOString(),
|
|
1772
|
+
}, 10 * 60_000)
|
|
1773
|
+
}
|
|
1774
|
+
if (!response.ok) return unavailable(`npm 下载统计接口返回 HTTP ${response.status}。`)
|
|
1775
|
+
let data
|
|
1776
|
+
try {
|
|
1777
|
+
data = await response.json()
|
|
1778
|
+
} catch {
|
|
1779
|
+
return unavailable('npm 下载统计返回了无法解析的数据。')
|
|
1780
|
+
}
|
|
1781
|
+
const points = Array.isArray(data.downloads)
|
|
1782
|
+
? data.downloads.map((point) => ({
|
|
1783
|
+
day: String(point && point.day ? point.day : ''),
|
|
1784
|
+
downloads: Number(point && point.downloads) || 0,
|
|
1785
|
+
}))
|
|
1786
|
+
: []
|
|
1787
|
+
return remember({
|
|
1788
|
+
package: name,
|
|
1789
|
+
start: data.start || (points.length > 0 ? points[0].day : null),
|
|
1790
|
+
end: data.end || (points.length > 0 ? points[points.length - 1].day : null),
|
|
1791
|
+
points,
|
|
1792
|
+
total: points.reduce((sum, point) => sum + point.downloads, 0),
|
|
1793
|
+
unavailable: false,
|
|
1794
|
+
empty: points.length === 0,
|
|
1795
|
+
fetchedAt: new Date().toISOString(),
|
|
1796
|
+
}, 10 * 60_000)
|
|
1797
|
+
}
|
|
1798
|
+
|
|
1728
1799
|
async function addTarget(rawDir) {
|
|
1729
1800
|
await ensureState()
|
|
1730
1801
|
const { dir } = await requireComponentDir(rawDir)
|
|
@@ -1765,6 +1836,9 @@ export function apply(ctx, config = {}) {
|
|
|
1765
1836
|
const { dir } = await requireComponentDir(url.searchParams.get('dir'))
|
|
1766
1837
|
return sendJson(response, 200, { ok: true, ...(await buildDetail(dir)) })
|
|
1767
1838
|
}
|
|
1839
|
+
if (pathname === `${API}/downloads`) {
|
|
1840
|
+
return sendJson(response, 200, { ok: true, ...(await getDownloads(url.searchParams.get('dir'))) })
|
|
1841
|
+
}
|
|
1768
1842
|
return sendJson(response, 404, { ok: false, code: 'not-found', message: '未知的发布管理接口。' })
|
|
1769
1843
|
}
|
|
1770
1844
|
if (request.method !== 'POST') {
|
package/locale/en.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"meta": {
|
|
3
|
-
"title": "Nanshan DSH publish
|
|
3
|
+
"title": "Nanshan DSH npm publish assistant",
|
|
4
4
|
"description": "Publish locally developed DSH components as npm packages: component cards, release settings, publish pre-checks, and a publish history."
|
|
5
5
|
},
|
|
6
|
-
"title": "Nanshan DSH publish
|
|
6
|
+
"title": "Nanshan DSH npm publish assistant",
|
|
7
7
|
"description": "Publish locally developed DSH components as npm packages: component cards, release settings, publish pre-checks, and a publish history."
|
|
8
8
|
}
|
package/locale/zh.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"meta": {
|
|
3
|
-
"title": "【南山】DSH
|
|
3
|
+
"title": "【南山】DSH发布Npm助手",
|
|
4
4
|
"description": "把本机开发的 DSH 组件按 npm 官方形式发布:组件清单、发布设置、发布预检查、发布记录。"
|
|
5
5
|
},
|
|
6
|
-
"title": "【南山】DSH
|
|
6
|
+
"title": "【南山】DSH发布Npm助手",
|
|
7
7
|
"description": "把本机开发的 DSH 组件按 npm 官方形式发布:组件清单、发布设置、发布预检查、发布记录。"
|
|
8
8
|
}
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wangnanshan/dsh-nanshan-publish-manager",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.6",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"description": "【南山】DSH
|
|
5
|
+
"description": "【南山】DSH发布Npm助手:以 npm 包(dsh.bundle + dsh.client)的官方形式,管理本机 DSH 组件的发布设置、发布预检查、npm 发布与发布记录。",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"keywords": [
|
|
8
8
|
"dsh",
|