dsh-theme-palette 1.3.0 → 1.3.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 +90 -3
- package/client.js +71 -5
- package/package.json +3 -2
- package/tools/release.mjs +117 -0
package/README.md
CHANGED
|
@@ -7,6 +7,48 @@ DSH Web / Desktop 的主题配色插件:**4 套配色 × 明暗两模式**,
|
|
|
7
7
|
|
|
8
8
|
当前版本 **0.8.2**(0.8.1 按深色专项审计重做深色侧,0.8.2 把强调色换成设计稿标准值;见 §修订记录)。
|
|
9
9
|
|
|
10
|
+
## 为什么配色会自己跳回默认(v1.3.1)
|
|
11
|
+
|
|
12
|
+
现象:设了暖砂,用了一阵(跑模型操作、切窗口、长时间放着),**突然变成黑白**,但设置页里
|
|
13
|
+
「暖砂」还是选中的。原因在宿主的 override 生命周期:
|
|
14
|
+
|
|
15
|
+
```js
|
|
16
|
+
// ThemeRuntime(宿主)
|
|
17
|
+
overrideTokens(source, tokens) {
|
|
18
|
+
const layer = { seq: this.overrideSeq++, tokens: validateOverrides(source, tokens) }
|
|
19
|
+
this.overrides.set(source, layer)
|
|
20
|
+
this.publish()
|
|
21
|
+
return () => { if (this.overrides.get(source) !== layer) return; this.overrides.delete(source); this.publish() }
|
|
22
|
+
}
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
而插件侧拿到的是被 runner 包装过的版本 —— **每一层的 disposer 都被挂在调用方的 fiber 上**:
|
|
26
|
+
|
|
27
|
+
```js
|
|
28
|
+
const dispose = Reflect.apply(method, target, [id, tokens])
|
|
29
|
+
ctx.effect(() => dispose, 'cordis-client-runner: dynamic theme override layer')
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
也就是说:**这一层的生命周期 = 插件 fiber 的生命周期**。插件被卸载/重载、或 fiber 的
|
|
33
|
+
effect 被清理时,层就没了;`composeActive` 在 `overrides.size === 0` 时直接返回内置主题,
|
|
34
|
+
于是页面**静默**变回默认色 —— localStorage 里的选择还在,设置页自然还显示暖砂。
|
|
35
|
+
|
|
36
|
+
会触发 fiber 重载的典型情况:动态插件被宿主重载(改过插件文件/profile、宿主自我更新)、
|
|
37
|
+
渲染进程恢复、以及某些"模型操作"引起的插件重载。
|
|
38
|
+
|
|
39
|
+
**v1.3.1 的处理**:加了一个自愈环。窗口重新可见 / 获焦 / `pageshow` / `theme/change` /
|
|
40
|
+
每 4 秒,回读 `body` 上的 5 个关键 token;不在了就把最后一层重放:
|
|
41
|
+
|
|
42
|
+
```
|
|
43
|
+
[dsh-theme-palette] 配色层不见了(visibility),已重放第 1 次
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
设置页顶部会显示"配色层被宿主丢过一次,已自动恢复 N 次";健康时不产生任何写入。
|
|
47
|
+
|
|
48
|
+
**另一种可能**(值得先分辨):如果是 **localStorage 被清**(换 profile、清缓存),设置页会回到
|
|
49
|
+
「黑白」选中态 —— 那种情况自愈救不回来,因为连"你想用哪套"都丢了。判据就是**设置页里选中的
|
|
50
|
+
是不是你原来那套**。
|
|
51
|
+
|
|
10
52
|
## 设置页重排(v1.3.0)
|
|
11
53
|
|
|
12
54
|
原来的设置页是六行"工程参数"堆在一起、每行都是同一种小药丸。v1.3.0 重做成四段:
|
|
@@ -496,12 +538,49 @@ alpha **浅色与内置一致、深色压到 0.6 倍**(见下):
|
|
|
496
538
|
|
|
497
539
|
## 安装
|
|
498
540
|
|
|
541
|
+
从 npm(推荐):
|
|
542
|
+
|
|
543
|
+
```bash
|
|
544
|
+
# 方式 A:装进 DSH profile
|
|
545
|
+
cd ~/.dsh/profiles/desktop
|
|
546
|
+
pnpm add dsh-theme-palette
|
|
547
|
+
|
|
548
|
+
# 方式 B:用 DSH 的插件命令
|
|
549
|
+
dsh plugin --profile desktop add dsh-theme-palette
|
|
550
|
+
```
|
|
551
|
+
|
|
552
|
+
从 GitHub Release 的 tarball(npm 不可达时):
|
|
553
|
+
|
|
554
|
+
```bash
|
|
555
|
+
pnpm add "https://github.com/zuoyunlai/dsh-theme-palette/releases/download/v1.3.1/dsh-theme-palette-1.3.1.tgz"
|
|
556
|
+
```
|
|
557
|
+
|
|
558
|
+
从源码(开发调试):
|
|
559
|
+
|
|
560
|
+
```bash
|
|
561
|
+
git clone https://github.com/zuoyunlai/dsh-theme-palette.git
|
|
562
|
+
dsh plugin --profile desktop add "$PWD/dsh-theme-palette"
|
|
499
563
|
```
|
|
500
|
-
|
|
564
|
+
|
|
565
|
+
装完**必须重启 GUI**(运行中的进程仍持旧模块)。
|
|
566
|
+
|
|
567
|
+
> 开发时注意:`node_modules/dsh-theme-palette` 是**副本**,改源码后要
|
|
568
|
+
> `rm -rf node_modules/dsh-theme-palette && pnpm install` —— `pnpm add` 对已存在的
|
|
569
|
+
> `file:` 目录不会刷新内容。
|
|
570
|
+
|
|
571
|
+
安装后:左下角头像 → 设置 → 左侧「**主题色**」。
|
|
572
|
+
|
|
573
|
+
## 发版
|
|
574
|
+
|
|
575
|
+
```bash
|
|
576
|
+
node tools/release.mjs --dry # 本地检查:语法 / 工作区 / 打包内容 / 版本是否已存在
|
|
577
|
+
node tools/release.mjs # 真发布(npm 已登录)+ 等 registry 放出 + 校验 latest
|
|
578
|
+
node tools/release.mjs --tag # 发布后再打 tag 并推 main + tag
|
|
501
579
|
```
|
|
502
580
|
|
|
503
|
-
|
|
504
|
-
|
|
581
|
+
脚本要处理三件容易出错的事:本机 npm 默认源是 npmmirror(**必须显式指官方源**);
|
|
582
|
+
新包名的首次发布会进**暂存审核**(CLI 照打印 `+ pkg@ver`,但 registry 要过几分钟才 200);
|
|
583
|
+
`latest` 不保证自动前移。2026-10-21 实测:提交到放出约 **8 分钟**。
|
|
505
584
|
|
|
506
585
|
## 结构
|
|
507
586
|
|
|
@@ -570,6 +649,14 @@ dsh plugin --profile desktop add "E:\Documents\deepseek-harness\default-workspac
|
|
|
570
649
|
- 苔绿强调 `155°→165°` —— 与底色(h≈152)从 2° 拉开到 13°,链接不再只靠明度识别
|
|
571
650
|
- 深色对比度:正文 14.40–14.98、三级 4.79 起、链接 4.65 起(0 处低于下限)
|
|
572
651
|
|
|
652
|
+
**1.3.2**
|
|
653
|
+
- README 安装说明改为 npm / Release tarball / 源码三条路径,去掉本机绝对路径
|
|
654
|
+
- 新增 `tools/release.mjs`:一键发版(检查 → 发布 → 等 registry → 校验 latest → 可选打 tag)
|
|
655
|
+
|
|
656
|
+
**1.3.1**
|
|
657
|
+
- 自愈:override 层被宿主丢掉时(插件 fiber 重载等)自动重放,设置页显示恢复次数
|
|
658
|
+
- 触发点:visibilitychange / focus / pageshow / theme/change / 4s 定时;健康时零写入
|
|
659
|
+
|
|
573
660
|
**1.3.0**
|
|
574
661
|
- 设置页重排:2×2 缩略图卡片(随明暗切换)+ 段落式设置行 + 分段控件 + 版本徽标
|
|
575
662
|
- 样式从内联改为独立注入的 `settings.css`(支持 hover / 窄屏 / `body[data-ds-dark-theme]`)
|
package/client.js
CHANGED
|
@@ -27,7 +27,7 @@ window.__ModuleLoader__.load({
|
|
|
27
27
|
const React = require('react')
|
|
28
28
|
const h = React.createElement
|
|
29
29
|
|
|
30
|
-
const PLUGIN_VERSION = '1.3.
|
|
30
|
+
const PLUGIN_VERSION = '1.3.2'
|
|
31
31
|
const STORE_KEY = 'dsh-theme-palette.v1'
|
|
32
32
|
const OVERRIDE_SOURCE = 'dsh-theme-palette'
|
|
33
33
|
|
|
@@ -1119,6 +1119,11 @@ body[data-ds-dark-theme] .dsh-tp-check{background:var(--t-accent-d);color:var(--
|
|
|
1119
1119
|
row(txt.sidebar, txt.sidebarSub, seg(SIDEBAR_MODES, sidebarMode, props.setSidebarMode)),
|
|
1120
1120
|
row(txt.rightbar, txt.rightbarSub, seg(RIGHTBAR_MODES, rightbarMode, props.setRightbarMode)),
|
|
1121
1121
|
health && !health.ok && h('p', { className: 'dsh-tp-status is-bad' }, txt.bad),
|
|
1122
|
+
health && health.ok && health.reapplies > 0 && h(
|
|
1123
|
+
'p',
|
|
1124
|
+
{ className: 'dsh-tp-status' },
|
|
1125
|
+
(isZh() ? '配色层被宿主丢过一次,已自动恢复 ' : 'Palette layer was dropped by the host; restored ') + health.reapplies + (isZh() ? ' 次' : 'x'),
|
|
1126
|
+
),
|
|
1122
1127
|
h('button', {
|
|
1123
1128
|
type: 'button',
|
|
1124
1129
|
className: 'dsh-tp-more',
|
|
@@ -1210,7 +1215,13 @@ body[data-ds-dark-theme] .dsh-tp-check{background:var(--t-accent-d);color:var(--
|
|
|
1210
1215
|
|
|
1211
1216
|
let disposeLayer = () => {}
|
|
1212
1217
|
/** 按当前配色 + 侧栏档位重建覆盖层(换配色与换侧栏档位走同一条路)。 */
|
|
1213
|
-
|
|
1218
|
+
// 最近一次成功应用的 token 表与原因:自愈时据此重放,也用来解释"为什么又变回去了"。
|
|
1219
|
+
let lastTokens = null
|
|
1220
|
+
let lastAppliedAt = 0
|
|
1221
|
+
let lastReason = 'init'
|
|
1222
|
+
let reapplyCount = 0
|
|
1223
|
+
|
|
1224
|
+
const applyAll = (reason) => {
|
|
1214
1225
|
const scheme = SCHEMES.find((s) => s.id === current) || SCHEMES[0]
|
|
1215
1226
|
const tokens = buildTokens(scheme, sidebarMode, textMode, rightbarMode, scrollMode, controlMode, mdMode)
|
|
1216
1227
|
let next = () => {}
|
|
@@ -1226,8 +1237,38 @@ body[data-ds-dark-theme] .dsh-tp-check{background:var(--t-accent-d);color:var(--
|
|
|
1226
1237
|
const prev = disposeLayer
|
|
1227
1238
|
disposeLayer = next
|
|
1228
1239
|
prev()
|
|
1240
|
+
if (reason !== undefined) lastReason = reason
|
|
1241
|
+
lastTokens = tokens
|
|
1242
|
+
lastAppliedAt = Date.now()
|
|
1229
1243
|
scheduleProbe(tokens)
|
|
1230
1244
|
}
|
|
1245
|
+
/**
|
|
1246
|
+
* 自愈。
|
|
1247
|
+
*
|
|
1248
|
+
* 宿主的 theme.overrideTokens 把每一层的 disposer 挂在**调用方 fiber** 上
|
|
1249
|
+
* (`ctx.effect(() => dispose, ...)`):插件被卸载/重载、或 fiber 的 effect 被清理时,
|
|
1250
|
+
* 这一层就没了 —— 表现正是"配色自己跳回默认",而 localStorage 里的选择还在。
|
|
1251
|
+
* 这里定期(以及窗口重新可见/获焦时)回读 body:token 不在了就把最后一层重放。
|
|
1252
|
+
*/
|
|
1253
|
+
const ensureApplied = (reason) => {
|
|
1254
|
+
if (lastTokens === null) return
|
|
1255
|
+
if (Date.now() - lastAppliedAt < 900) return
|
|
1256
|
+
let missing = false
|
|
1257
|
+
try {
|
|
1258
|
+
const style = getComputedStyle(document.body)
|
|
1259
|
+
for (const name of TOKEN_PROBE) {
|
|
1260
|
+
if (!(name in lastTokens)) continue
|
|
1261
|
+
if (style.getPropertyValue(name).trim() === '') { missing = true; break }
|
|
1262
|
+
}
|
|
1263
|
+
} catch (err) {
|
|
1264
|
+
missing = true
|
|
1265
|
+
}
|
|
1266
|
+
if (!missing) return
|
|
1267
|
+
reapplyCount += 1
|
|
1268
|
+
console.info('[dsh-theme-palette] 配色层不见了(' + reason + '),已重放第 ' + reapplyCount + ' 次')
|
|
1269
|
+
applyAll('self-heal:' + reason)
|
|
1270
|
+
}
|
|
1271
|
+
|
|
1231
1272
|
let health = null
|
|
1232
1273
|
const healthListeners = new Set()
|
|
1233
1274
|
const getHealth = () => health
|
|
@@ -1243,7 +1284,7 @@ body[data-ds-dark-theme] .dsh-tp-check{background:var(--t-accent-d);color:var(--
|
|
|
1243
1284
|
const next = probeApplied(tokens)
|
|
1244
1285
|
if (next === null) return
|
|
1245
1286
|
if (health !== null && health.ok === next.ok && health.checked === next.checked) return
|
|
1246
|
-
health = next
|
|
1287
|
+
health = { ...next, reapplies: reapplyCount }
|
|
1247
1288
|
if (!next.ok) console.warn('[dsh-theme-palette] 有 token 没写进 body,宿主可能改名了')
|
|
1248
1289
|
emitHealth()
|
|
1249
1290
|
}
|
|
@@ -1321,11 +1362,36 @@ body[data-ds-dark-theme] .dsh-tp-check{background:var(--t-accent-d);color:var(--
|
|
|
1321
1362
|
console.warn('[dsh-theme-palette] dev hook attach 失败:', err)
|
|
1322
1363
|
}
|
|
1323
1364
|
|
|
1365
|
+
// 自愈的触发点:窗口回前台、获焦、页面恢复、主题事件、以及一个低频定时器。
|
|
1366
|
+
const heal = (why) => () => { try { ensureApplied(why) } catch (err) { /* 自愈失败不影响主流程 */ } }
|
|
1367
|
+
const healOnVisible = heal('visibility')
|
|
1368
|
+
const healOnFocus = heal('focus')
|
|
1369
|
+
const healOnShow = heal('pageshow')
|
|
1370
|
+
const healOnTheme = heal('theme-change')
|
|
1371
|
+
let healTimer = null
|
|
1372
|
+
try {
|
|
1373
|
+
document.addEventListener('visibilitychange', healOnVisible)
|
|
1374
|
+
window.addEventListener('focus', healOnFocus)
|
|
1375
|
+
window.addEventListener('pageshow', healOnShow)
|
|
1376
|
+
} catch (err) { /* 无 DOM 时跳过 */ }
|
|
1377
|
+
try { ctx.on('theme/change', healOnTheme) } catch (err) { /* 事件名变了就只靠定时器 */ }
|
|
1378
|
+
try { healTimer = setInterval(() => ensureApplied('interval'), 4000) } catch (err) { /* 无定时器环境 */ }
|
|
1379
|
+
|
|
1324
1380
|
ctx.effect(() => {
|
|
1325
|
-
applyAll()
|
|
1381
|
+
applyAll('boot')
|
|
1326
1382
|
const removeStyle = installRightbarStyle()
|
|
1327
1383
|
const removeSettings = installSettingsStyle()
|
|
1328
|
-
return () => {
|
|
1384
|
+
return () => {
|
|
1385
|
+
disposeLayer()
|
|
1386
|
+
removeStyle()
|
|
1387
|
+
removeSettings()
|
|
1388
|
+
try {
|
|
1389
|
+
document.removeEventListener('visibilitychange', healOnVisible)
|
|
1390
|
+
window.removeEventListener('focus', healOnFocus)
|
|
1391
|
+
window.removeEventListener('pageshow', healOnShow)
|
|
1392
|
+
} catch (err) { /* 同上 */ }
|
|
1393
|
+
if (healTimer !== null) { clearInterval(healTimer); healTimer = null }
|
|
1394
|
+
}
|
|
1329
1395
|
}, 'dsh-theme-palette: token layer + 右栏样式')
|
|
1330
1396
|
|
|
1331
1397
|
ctx.slots.inject('settings.section', () =>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-theme-palette",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.2",
|
|
4
4
|
"description": "DSH 主题配色:4 套配色 × 明暗两模式,OKLCH 色域安全重建 + 真实承载面对比度求解,可调强度与面板表面。",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.js",
|
|
@@ -22,7 +22,8 @@
|
|
|
22
22
|
"tools/shoot-html.mjs",
|
|
23
23
|
"tools/preview-settings.mjs",
|
|
24
24
|
"tools/shoot-desktop.mjs",
|
|
25
|
-
"tools/compare.mjs"
|
|
25
|
+
"tools/compare.mjs",
|
|
26
|
+
"tools/release.mjs"
|
|
26
27
|
],
|
|
27
28
|
"peerDependencies": {
|
|
28
29
|
"@deepseek-ai/cordis": "^4.0.0"
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 一键发版:npm publish + 等 registry 出现该版本 + 校验 dist-tag + 可选打 tag 推 Git。
|
|
3
|
+
*
|
|
4
|
+
* 用法:
|
|
5
|
+
* node tools/release.mjs --dry # 只做本地检查(语法、打包内容、版本可用性)
|
|
6
|
+
* node tools/release.mjs # 真发布(需要 npm 登录态)
|
|
7
|
+
* node tools/release.mjs --tag # 发布后再打 tag 并推 main + tag
|
|
8
|
+
* node tools/release.mjs --skip-publish # 只打 tag(版本已经在 registry 上了)
|
|
9
|
+
*
|
|
10
|
+
* 为什么需要它:本机 npm 默认源是 npmmirror,publish 必须显式指官方源;而 npm 对新包名
|
|
11
|
+
* 会走「暂存审核」,CLI 照样打印 `+ pkg@ver`,必须回查 registry 才知道有没有真的放出来。
|
|
12
|
+
* 另外 latest 不保证自动前移,得验一次。
|
|
13
|
+
*/
|
|
14
|
+
import { execFileSync } from 'node:child_process'
|
|
15
|
+
import { readFileSync } from 'node:fs'
|
|
16
|
+
import path from 'node:path'
|
|
17
|
+
import { fileURLToPath } from 'node:url'
|
|
18
|
+
|
|
19
|
+
const here = path.dirname(fileURLToPath(import.meta.url))
|
|
20
|
+
const root = path.resolve(here, '..')
|
|
21
|
+
const args = process.argv.slice(2)
|
|
22
|
+
const has = (f) => args.includes('--' + f)
|
|
23
|
+
|
|
24
|
+
const REGISTRY = 'https://registry.npmjs.org'
|
|
25
|
+
// Windows 上 `npm` 实际是 npm.cmd,execFileSync 不做 PATHEXT 解析,必须写全名。
|
|
26
|
+
const NPM = process.platform === 'win32' ? 'npm.cmd' : 'npm'
|
|
27
|
+
const CURL = process.platform === 'win32' ? 'curl.exe' : 'curl'
|
|
28
|
+
const pkg = JSON.parse(readFileSync(path.join(root, 'package.json'), 'utf8'))
|
|
29
|
+
const { name, version } = pkg
|
|
30
|
+
const tag = 'v' + version
|
|
31
|
+
|
|
32
|
+
const run = (cmd, cmdArgs, opts = {}) => {
|
|
33
|
+
const out = execFileSync(cmd, cmdArgs, { cwd: root, encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'], ...opts })
|
|
34
|
+
return typeof out === 'string' ? out.trim() : ''
|
|
35
|
+
}
|
|
36
|
+
const sh = (cmd) => run(process.platform === 'win32' ? 'cmd' : 'sh', process.platform === 'win32' ? ['/c', cmd] : ['-c', cmd])
|
|
37
|
+
const ok = (m) => console.log(' ✓ ' + m)
|
|
38
|
+
const die = (m) => { console.error(' ✗ ' + m); process.exit(1) }
|
|
39
|
+
|
|
40
|
+
console.log(`\n发版 ${name}@${version}${has('dry') ? '(dry run)' : ''}\n`)
|
|
41
|
+
|
|
42
|
+
// 1. 本地检查
|
|
43
|
+
try { run(process.execPath, ['--check', 'client.js']); ok('client.js 语法') } catch { die('client.js 语法错误') }
|
|
44
|
+
const dirty = sh('git status --porcelain')
|
|
45
|
+
if (dirty) die('工作区不干净,先提交:\n' + dirty)
|
|
46
|
+
ok('工作区干净')
|
|
47
|
+
|
|
48
|
+
const packed = run(process.platform === 'win32' ? 'cmd' : 'sh',
|
|
49
|
+
process.platform === 'win32' ? ['/c', 'npm pack --dry-run --json'] : ['-c', 'npm pack --dry-run --json'])
|
|
50
|
+
try {
|
|
51
|
+
const info = JSON.parse(packed.slice(packed.indexOf('[')))[0]
|
|
52
|
+
ok(`打包 ${info.entryCount} 个文件 / ${(info.size / 1024).toFixed(1)} KB`)
|
|
53
|
+
} catch { die('读不出 npm pack --dry-run 的结果') }
|
|
54
|
+
|
|
55
|
+
// 2. 版本是否已在 registry
|
|
56
|
+
let remote = null
|
|
57
|
+
try { remote = JSON.parse(sh(`${NPM} view ${name} versions --json --registry=${REGISTRY}`)) } catch { remote = null }
|
|
58
|
+
const remoteList = Array.isArray(remote) ? remote : remote ? [remote] : []
|
|
59
|
+
if (remoteList.includes(version)) {
|
|
60
|
+
ok(`registry 上已有 ${version}`)
|
|
61
|
+
if (!has('skip-publish')) die('该版本已发布,先改 package.json 的 version')
|
|
62
|
+
} else {
|
|
63
|
+
ok(`registry 上没有 ${version}(当前 ${remoteList.length} 个版本)`)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (has('dry')) { console.log('\n干跑结束,没有发布。\n'); process.exit(0) }
|
|
67
|
+
|
|
68
|
+
// 3. 发布
|
|
69
|
+
if (!has('skip-publish')) {
|
|
70
|
+
console.log('\n发布中…')
|
|
71
|
+
try {
|
|
72
|
+
// Node 20+ 在 Windows 上不允许直接 spawn .cmd(CVE-2024-27980 的缓解),
|
|
73
|
+
// 必须走 shell;所以统一用 sh(),而不是 execFileSync。
|
|
74
|
+
const out = sh(`${NPM} publish --registry=${REGISTRY}`)
|
|
75
|
+
if (out) console.log(out)
|
|
76
|
+
} catch (err) {
|
|
77
|
+
die('npm publish 失败:' + (err.stderr || err.message))
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// 4. 回查 registry(新包会进暂存审核,CLI 打印成功不代表已放出)
|
|
82
|
+
console.log('\n等 registry 出现该版本(最多 10 分钟,暂存审核通过前会一直 404)…')
|
|
83
|
+
const started = Date.now()
|
|
84
|
+
let live = false
|
|
85
|
+
while (Date.now() - started < 10 * 60 * 1000) {
|
|
86
|
+
try {
|
|
87
|
+
const code = sh(`${CURL} -s -o ${process.platform === 'win32' ? 'NUL' : '/dev/null'} -w "%{http_code}" ${REGISTRY}/${name}/${version}`)
|
|
88
|
+
if (code.trim() === '200') { live = true; break }
|
|
89
|
+
} catch { /* 网络抖动继续等 */ }
|
|
90
|
+
await new Promise((r) => setTimeout(r, 15000))
|
|
91
|
+
}
|
|
92
|
+
if (!live) die('10 分钟内没出现在 registry —— 可能仍在审核,稍后手动跑 npm view ' + name + ' versions')
|
|
93
|
+
ok(`registry 上已可下载 ${version}`)
|
|
94
|
+
|
|
95
|
+
// 5. dist-tag
|
|
96
|
+
const latest = sh(`${NPM} view ${name} dist-tags.latest --registry=${REGISTRY}`).trim()
|
|
97
|
+
if (latest === version) ok(`latest = ${version}`)
|
|
98
|
+
else {
|
|
99
|
+
console.log(` ! latest 指向 ${latest},手工前移:npm dist-tag add ${name}@${version} latest --registry=${REGISTRY}`)
|
|
100
|
+
try {
|
|
101
|
+
sh(`${NPM} dist-tag add ${name}@${version} latest --registry=${REGISTRY}`)
|
|
102
|
+
ok(`已前移 latest → ${version}`)
|
|
103
|
+
} catch { console.log(' ! 前移失败,需要人工处理') }
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// 6. Git tag(可选)
|
|
107
|
+
if (has('tag')) {
|
|
108
|
+
try {
|
|
109
|
+
sh(`git tag -a ${tag} -m "${name} ${version}"`)
|
|
110
|
+
sh(`git push origin main ${tag}`)
|
|
111
|
+
ok(`已打 tag 并推送 ${tag}`)
|
|
112
|
+
} catch (err) {
|
|
113
|
+
console.log(' ! tag/推送失败:' + (err.stderr || err.message))
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
console.log(`\n完成:https://www.npmjs.com/package/${name}/v/${version}\n`)
|