dsh-m 0.9.8 → 0.9.10
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.en.md +12 -0
- package/README.md +12 -0
- package/lib/core/profile-ops.js +32 -0
- package/lib/core/registry.js +7 -2
- package/package.json +1 -1
package/README.en.md
CHANGED
|
@@ -48,6 +48,18 @@ The default registry includes DSH Skins, ModSearch, the Lark / QQ / Weixin / WeC
|
|
|
48
48
|
|
|
49
49
|
The restart chain below applies to **Web only** (Desktop takes effect via the official app lifecycle: after installing, quit and reopen the Desktop app — see the 0.9.0 section). After any mutation, the already-open Market and Installed views refresh the profile state together, keeping badges and cards in sync without closing and reopening the marketplace; a "⚡ Restart" banner appears — under systemd, the DSH launcher's `appExit` hook hands the restart back to a unit configured with `Restart=on-failure` or `Restart=always`, avoiding a `systemctl` helper inside the unit cgroup that is about to stop; if `appExit` is unavailable, the fallback uses a manager-owned transient `systemd-run` service and only then a detached helper. The client confirms the replacement by boot id and dismisses the banner, leaving DSH Web's own background connection recovery in control; it does not force a full-page reload during the auth/route handoff. The restart chain has been live-verified on the current DSH Web `0.2.0-rc.2` (2026-10-01): dsh-m probes the unit's `Restart=` policy, hands the stop back via `appExit`, the `status=75/TEMPFAIL` exit is caught by the Restart policy and auto-started, and after recovery the page reconnects in the background with the panel fully functional. Historical scope: the `0.1.5-rc.1` era covered live `/dshm` ping and authenticated `303 → 200` checks; `0.1.2-rc.1` is covered by contract checks plus runtime shape-detection fallback (see the verified array) — with the deployment moved on, no live E2E is chased on retired generations; the transient `systemd-run` fallback only applies to hosts without `appExit` (all supported generations provide it) and stays a documented fallback rather than a release gate; repeated install/uninstall is covered by the profile-transaction test surface (compensation, post-install guard, 813 cases) plus the 0.4.0→0.4.2 consecutive-release evidence. Installs stream live pnpm progress (resolve → download → link → build).
|
|
50
50
|
|
|
51
|
+
### Fixed in 0.9.10 — Desktop self-upgrade hitting the official supply-chain release-age policy now yields an honest wait guide
|
|
52
|
+
|
|
53
|
+
- **Scenario**: clicking the upgrade chip routes through the official manager, whose pnpm supply-chain policy (`minimumReleaseAge`, releases must age 24h before installation) rejected the just-published version — `ERR_PNPM_MINIMUM_RELEASE_AGE_VIOLATION`. **The policy is working as designed** (anti-supply-chain-attack release waiting period); dsh-m just dumped a screen of raw pnpm output as a generic failure.
|
|
54
|
+
- **Fix**: the manager-result judgment recognizes the policy code and translates it into an honest guide — naming the pending entry and its publish time, deriving when a retry will pass, and offering the interim path (install the same version from DSH Web). No policy bypass, ever (the waiting period is the anti-attack red line; same stance as dsh-market #732).
|
|
55
|
+
- Also: community chain ⑨ hang-cap assertion 2s→10s (timer starvation under the full parallel suite tripped it twice; the "never hangs forever" contract is unchanged).
|
|
56
|
+
|
|
57
|
+
### Fixed in 0.9.9 — No more red "primary route failed" notice once the mirror line took over (notice convergence)
|
|
58
|
+
|
|
59
|
+
- **Scenario**: with the default dual-line registry (raw → jsDelivr), when the primary `default-raw` failed and the mirror succeeded, the settings page still showed a red "Remote notice: default-raw 失败:fetch failed…可稍后重试或检查网络后重试" — alarming against data that had already self-healed, with advice that didn't apply (intermittent raw.githubusercontent.com unreachability is exactly why the mirror line exists).
|
|
60
|
+
- **Fix**: a later line succeeding means the earlier failure self-healed — `errors` no longer carries it (the "effective source" row already states the live line, e.g. "GitHub 镜像(备用)"). When ALL lines fail into cache/bundled, the errors are still kept — that's the actionable signal. The custom chain (custom source failing into cache) keeps its warning, unchanged.
|
|
61
|
+
- Added a test seam for the default chain (`defaultRoutes` override); regression gates cover both convergence and the keep-on-total-failure cases.
|
|
62
|
+
|
|
51
63
|
### Fixed in 0.9.8 — Desktop package operations fully wired to the official manager (install always failed; upgrade/uninstall/self-upgrade opened)
|
|
52
64
|
|
|
53
65
|
- **Why every install failed**: the Host API's desktop install branch called `desktopInstall(id, cfg, opts)` without the 4th `deps` argument — `getService` never reached the adapter, so desktop installs always failed with "official pluginManager service unavailable (fail-closed)" (100% reproducible, timing irrelevant). The `dshm_install` tool had the same defect.
|
package/README.md
CHANGED
|
@@ -48,6 +48,18 @@
|
|
|
48
48
|
|
|
49
49
|
以下重启链路仅适用于 **Web**(Desktop 的生效走官方应用生命周期:安装完成后退出并重新打开 Desktop 应用即可,见 0.9.0 节)。安装 / 卸载 / 升级完成后,当前已打开的市场页与已装页会一起重新读取 profile 状态并同步徽标/卡片,不需要关闭后重新打开插件市场;随后出现「⚡ 一键重启」横幅——受 systemd 管理时通过 DSH launcher 的 `appExit` 交给服务的 `Restart` 策略,避免在待停止 unit 的 cgroup 内启动 `systemctl` helper;无 `appExit` 的 systemd 兜底改用 manager-owned transient `systemd-run`,最后才退回 detached-helper。客户端按 boot id 确认新进程已恢复后关闭横幅,交由 DSH Web 自身的后台连接重试恢复页面,不强制整页刷新,避免认证/路由切换期间白屏。重启链路已在当前 DSH Web `0.2.0-rc.2` 实机核验(2026-10-01):dsh-m 探测 unit `Restart=` 策略后经 `appExit` 交还 systemd,`status=75/TEMPFAIL` 退出由 Restart 策略接住自动拉起,服务恢复后页面后台重连、面板全功能可用。历史口径:`0.1.5-rc.1` 时代已核验 `/dshm` ping 与带认证 `303 → 200`;`0.1.2-rc.1` 按契约核对 + 形态探测兜底收录(verified 数组),部署面演进后不再追旧代 live E2E;transient `systemd-run` 兜底仅适用于无 `appExit` 的宿主(受支持代际均提供 `appExit`),保持设计兜底而非发布门槛;连续安装/卸载由 profile 事务测试面(补偿事务、装后守卫等 813 用例)与 0.4.0→0.4.2 连续发布实证覆盖。安装过程实时显示 pnpm 进度(解析 → 下载 → 链接 → 构建)。
|
|
50
50
|
|
|
51
|
+
### 0.9.10 修复:Desktop 自升级撞上官方供应链等待期(minimumReleaseAge)→ 诚实指引发成可读
|
|
52
|
+
|
|
53
|
+
- **场景**:点升级角标走官方管理器时,desktop profile 的 pnpm 供应链策略(`minimumReleaseAge`,发布满 24h 才可安装)拒绝了刚发布的版本——`ERR_PNPM_MINIMUM_RELEASE_AGE_VIOLATION`。**这是策略在正确工作**(防供应链攻击的发布等待期),不是故障;但 dsh-m 此前把它当普通失败甩一屏 pnpm 原文。
|
|
54
|
+
- **修复**:管理器结果判定识别该策略码,翻译成诚实指引——点名等待期内的条目与发布时刻、按策略推算「预计何时可重试」,并给出等待期内的替代路径(DSH Web 安装同版本)。绝不做策略绕过(等待期是防供应链攻击的红线,dsh-market #732 同态度)。
|
|
55
|
+
- 附带:community 获取链 ⑨ 的挂起上限断言 2s→10s(全量套件并行定时器饥饿下两次闪断;契约「不永久挂起」不变)。
|
|
56
|
+
|
|
57
|
+
### 0.9.9 修复:备用线路接住后不再弹红色「主线路失败」提示(提示收敛)
|
|
58
|
+
|
|
59
|
+
- **场景**:默认精选清单双线路(raw → jsDelivr)里主线路 `default-raw` 失败、备用线路成功接管时,设置页仍弹红色「远端提示:default-raw 失败:fetch failed…可稍后重试或检查网络后重试」——对着已经自愈的数据报警,还带着不成立的建议(大陆网络下 raw 间歇不可达是常态,这正是备用线路存在的原因)。
|
|
60
|
+
- **修复**:后续线路成功 = 先行失败已自愈,`errors` 不再携带(设置页「生效来源」行已如实标注当前线路,如「GitHub 镜像(备用)」);全线路失败落 cache/bundled 时错误照常保留——那才是需要行动的信号。custom 链(自定义源失败回退缓存)行为不变,仍然提示。
|
|
61
|
+
- 顺带补了 default 链的测试缝(`defaultRoutes` 覆写),双线路收敛与全挂保留各有回归门。
|
|
62
|
+
|
|
51
63
|
### 0.9.8 修复:Desktop 包操作全面接通官方管理器(安装恒 no-manager;升级/卸载/自升级开放)
|
|
52
64
|
|
|
53
65
|
- **安装恒失败的根因**:Host API 的 desktop 安装分支调 `desktopInstall(id, cfg, opts)` 漏传第 4 参 deps——`getService` 根本没进适配器,desktop 安装恒报「官方 pluginManager 服务不可用(fail-closed)」(100% 必现,与时机无关)。工具面 `dshm_install` 同病。
|
package/lib/core/profile-ops.js
CHANGED
|
@@ -26,6 +26,33 @@ import { listInstalledPlugins as defaultListInstalled } from './installed.js';
|
|
|
26
26
|
import { precheckNpmCompat, IncompatibleError } from './compat-check.js';
|
|
27
27
|
import { npmLatest as defaultNpmLatest, githubLatestTag as defaultGithubLatestTag } from './versions.js';
|
|
28
28
|
import { togglePlugin, ToggleError } from './toggle.js';
|
|
29
|
+
function humanDuration(ms) {
|
|
30
|
+
if (!Number.isFinite(ms) || ms <= 0)
|
|
31
|
+
return '不到 1 分钟';
|
|
32
|
+
const minutes = Math.round(ms / 60_000);
|
|
33
|
+
if (minutes < 60)
|
|
34
|
+
return `${minutes} 分钟`;
|
|
35
|
+
const hours = Math.floor(minutes / 60);
|
|
36
|
+
const rest = minutes % 60;
|
|
37
|
+
return rest ? `${hours} 小时 ${rest} 分钟` : `${hours} 小时`;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* 0.9.10:desktop profile 的 pnpm 供应链策略(minimumReleaseAge)拒绝「太新鲜」的版本——
|
|
41
|
+
* 这是策略在正确工作(防供应链攻击的发布等待期),不是环境故障。解析出条目/发布时刻/
|
|
42
|
+
* 策略截止,翻译成「何时可重试」的诚实指引,而不是甩一屏 pnpm 原文(dsh-market #732 同态度)。
|
|
43
|
+
*/
|
|
44
|
+
function releaseAgeWaitMessage(diagnostic) {
|
|
45
|
+
const m = /(\S+@\S+) was published at ([^,]+), within the minimumReleaseAge cutoff \(([^)]+)\)/.exec(diagnostic);
|
|
46
|
+
if (!m) {
|
|
47
|
+
return '官方管理器的供应链策略(minimumReleaseAge)未满足:新发布的版本需满等待期才能装入 desktop profile。稍后再点升级即可;等待期内也可改用 DSH Web 安装';
|
|
48
|
+
}
|
|
49
|
+
const entry = m[1];
|
|
50
|
+
const publishedAt = Date.parse(m[2]);
|
|
51
|
+
const cutoff = Date.parse(m[3]);
|
|
52
|
+
const deadline = Number.isFinite(publishedAt) && Number.isFinite(cutoff) ? publishedAt + (Date.now() - cutoff) : NaN;
|
|
53
|
+
const when = Number.isFinite(deadline) ? `约 ${humanDuration(deadline - Date.now())}后(${new Date(deadline).toLocaleString('zh-CN', { hour12: false })})` : '稍后';
|
|
54
|
+
return `官方管理器的供应链策略(minimumReleaseAge)要求新发布版本满等待期才能安装:${entry} 尚在等待期内,预计 ${when}可重试。等待期内也可改用 DSH Web 安装同版本`;
|
|
55
|
+
}
|
|
29
56
|
export class DesktopOpsError extends Error {
|
|
30
57
|
code;
|
|
31
58
|
constructor(code, message) {
|
|
@@ -167,6 +194,11 @@ async function runManagedInstall(service, pkg, spec, approvedBuilds) {
|
|
|
167
194
|
if (change.packageResult?.kind === 'build-blocked' && pending.length > 0) {
|
|
168
195
|
throw new DesktopBuildApprovalNeeded(pending);
|
|
169
196
|
}
|
|
197
|
+
// 0.9.10:供应链等待期(minimumReleaseAge)——策略正确工作,翻译成「何时可重试」
|
|
198
|
+
const diagnosticText = `${change.error?.diagnostic ?? ''}\n${change.error?.code ?? ''}`;
|
|
199
|
+
if (/ERR_PNPM_MINIMUM_RELEASE_AGE_VIOLATION|minimumReleaseAge/i.test(diagnosticText)) {
|
|
200
|
+
throw new DesktopOpsError('release-age-wait', releaseAgeWaitMessage(diagnosticText));
|
|
201
|
+
}
|
|
170
202
|
if (change.stage === 'enable') {
|
|
171
203
|
throw new DesktopOpsError('enable-failed', `安装后启用阶段失败(${pkg};application=failed、stage=enable${change.error?.code ? `、error=${change.error.code}` : ''}):包已写入但未能启用,请在官方 Desktop 插件页查看状态或重试`);
|
|
172
204
|
}
|
package/lib/core/registry.js
CHANGED
|
@@ -586,7 +586,8 @@ function loadedFromCacheFile(file, configuredAddress, errors) {
|
|
|
586
586
|
async function loadDefaultChain(cfg, opts, chain) {
|
|
587
587
|
const errors = [];
|
|
588
588
|
const startedAt = Date.now();
|
|
589
|
-
|
|
589
|
+
const routes = opts.defaultRoutes ?? DEFAULT_URLS;
|
|
590
|
+
for (const candidate of routes) {
|
|
590
591
|
try {
|
|
591
592
|
const { registry, finalUrl } = await fetchRegistryFromUrl(candidate.url, attemptTimeoutMs(cfg, opts, startedAt), opts.signal);
|
|
592
593
|
const fetchedAt = new Date().toISOString();
|
|
@@ -607,7 +608,11 @@ async function loadDefaultChain(cfg, opts, chain) {
|
|
|
607
608
|
source: candidate.source,
|
|
608
609
|
status: 'ready',
|
|
609
610
|
fetchedAt,
|
|
610
|
-
|
|
611
|
+
// 0.9.9:后续线路成功 = 先行的线路失败已自愈,不再作为 error 上屏——
|
|
612
|
+
// 「生效来源」行已如实标注当前线路(如 GitHub 镜像(备用));那条红色
|
|
613
|
+
// 「default-raw 失败…可检查网络」对着成功自愈的数据纯属报警噪音(主人 2026-10-01)。
|
|
614
|
+
// 全线路失败落 cache/bundled 时 errors 照常保留——那才是需要行动的信号。
|
|
615
|
+
errors: [],
|
|
611
616
|
count: registry.plugins.length,
|
|
612
617
|
}),
|
|
613
618
|
registry,
|