dsh-m 0.9.93 → 0.9.94
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/CHANGELOG.md +12 -0
- package/lib/client.js +72 -80
- package/lib/core/profile-transaction.js +31 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,12 @@ The full release history of dsh-m, maintained bilingually: **Chinese first, Engl
|
|
|
8
8
|
|
|
9
9
|
## 中文
|
|
10
10
|
|
|
11
|
+
### 0.9.94 重构+修复:变更结局描述客户端单源化(op-outcome)+ 补丁映射 YAML 引号宽容与重复键根治
|
|
12
|
+
|
|
13
|
+
- **需求(2026-10-17 架构巡查 + grill 九决议)**:变更结局的呈现无 owning module——守卫拦截文案装配 main.jsx 三处逐字重复(0.9.93 toWire 收敛了线格式、client 侧呈现是已知残尾,`repairBasis` 后加时的多点位手补复发已实证)、升级成功文案两处逐字、builds 后缀四处拼装、superseded 五处;另:事务补偿对 workspace `patchedDependencies` **非双引号在场形态**(pnpm 的单引号/无引号书写)逐字判重失明——逐字判重可自造重复键文件、复读检查可假 committed(同包链经 strip 先摘在生产不可达,本项属防御面收敛与 fail-closed 补洞)。
|
|
14
|
+
- **方案**:新纯模块 `src/client/op-outcome.js`(GLOSSARY「变更结局描述」入册)——工厂 `createOpOutcome(lookup)` 出成品文案(`createMarkdown(h)` 同款注入先例),`guardBlockedText`/`buildsSuffix`/`upgradeOkText`/`mutationFailed` 四函数;六处接线站点(五类投影)一行接线,流程特有分支(兼容弹窗/能力表拒绝/乐观提示)与 ToolCardRow 短版留守注记。D9:`verifyPatchMapping` workspace 支改 yaml 键成员判定(解析失败 fail-closed 报「复读失败」);`restorePatchMapping` 追加前解析去重(同键任意引号形态在场即跳过,解析失败退化逐字判重)。
|
|
15
|
+
- **验证**:全量 1567 测试全绿(新增 client-op-outcome 参数化矩阵 32 例——guardBlockedText/buildsSuffix/upgradeOkText to×activation 3×3 全交叉/mutationFailed × verb 五枚,期望串全手写字面量;事务补丁映射五钉例——单引号去重/无引号去重/模拟 pnpm 解析炸端到端/坏 YAML 假 committed→fail-closed/restore 退化不抛,夹具异包前提钉死)、typecheck 零错误;收敛锚 main.jsx `join(" | ")` 3→0、`mutationFailed` ≥ 6、`upgradeNotify` 解构随调用点清除;实施经计划评审四轮 15 项收敛 + 硬停点执行评审两轮(基线独立树复跑 1530 对照 + TDD 红→绿独立复演)后放行;实施计划见 `docs/plans/2026-10-17-op-outcome-and-verify-quote-fix-implementation-plan.md`。
|
|
16
|
+
|
|
11
17
|
### 0.9.93 重构:装后守卫线格式收敛至 InstallGuardError.toWire——加守卫字段编辑点 8 → 2(纯重构,线格式语义零变化)
|
|
12
18
|
|
|
13
19
|
- **需求(2026-10-16 架构巡查摩擦点 3,巡查首推)**:`InstallGuardError` → 线格式的投影在手写 5 处逐字重复(host-api 409 payload / tools install+upgrade 两臂 / cli install+upgrade 两臂)+ client 再拼一次——加一个守卫字段要碰 8 处(类 + 5 投影 + client 读取 + 测试),`repairBasis` 后加时各点手补已实证过一次复发。
|
|
@@ -655,6 +661,12 @@ The full release history of dsh-m, maintained bilingually: **Chinese first, Engl
|
|
|
655
661
|
|
|
656
662
|
## English
|
|
657
663
|
|
|
664
|
+
### Refactor + fix in 0.9.94 — client-side mutation-outcome copy single-sourced (op-outcome) + patch-mapping YAML quote tolerance and duplicate-key elimination
|
|
665
|
+
|
|
666
|
+
- **Need (2026-10-17 architecture walk + nine grilled decisions)**: the presentation of mutation outcomes had no owning module — the guard-interception copy was assembled verbatim three times in main.jsx (0.9.93's toWire converged the wire format while the client-side presentation remained the known leftover; the multi-point hand-patching recurrence when `repairBasis` was added had already been demonstrated once), the upgrade-success copy twice verbatim, the builds suffix four times, superseded five times; separately: the transaction's compensation was blind to **non-double-quoted in-place forms** of workspace `patchedDependencies` (pnpm's single-quoted/unquoted spellings) via verbatim duplicate checks — the verbatim check could fabricate a duplicate-key file and the read-back check could false-pass (the same-package chain is unreachable in production because strip removes it first; this item is defensive-surface convergence and a fail-closed gap-fill).
|
|
667
|
+
- **Approach**: a new pure module `src/client/op-outcome.js` (GLOSSARY term "Mutation Outcome Copy" registered) — a factory `createOpOutcome(lookup)` producing finished copy (the `createMarkdown(h)` injection precedent) with four functions `guardBlockedText`/`buildsSuffix`/`upgradeOkText`/`mutationFailed`; six wiring sites (five projection families) become one-line calls, while flow-specific branches (compat dialog / capability-rejection banner / optimistic note) and the ToolCardRow short variant stay behind with an annotation. D9: the workspace leg of `verifyPatchMapping` now judges key membership via real yaml parsing (parse failure fails closed with "read-back failed"); `restorePatchMapping` parse-dedupes before appending (skip when the key exists in any quoting form; on parse failure it degrades to today's verbatim line check).
|
|
668
|
+
- **Verification**: full suite 1567 green (new client-op-outcome parametrized matrix of 32 cases — guardBlockedText/buildsSuffix/upgradeOkText to×activation 3×3 full cross/mutationFailed × five verbs, expected strings all hand-written literals; five transaction patch-mapping pins — single-quote dedupe / unquoted dedupe / simulated pnpm parse-explosion end-to-end / malformed-YAML false-committed → fail-closed / restore degradation does not throw, with the cross-package fixture premise pinned), typecheck clean; convergence anchors in main.jsx: `join(" | ")` 3→0, `mutationFailed` ≥ 6, and the `upgradeNotify` destructure removed along with its call sites; released after a four-round 15-finding plan review plus a hard-stopped two-round execution review (baseline independently re-run at 1530 for comparison + TDD red→green independently re-enacted); implementation plan in `docs/plans/2026-10-17-op-outcome-and-verify-quote-fix-implementation-plan.md`.
|
|
669
|
+
|
|
658
670
|
### Refactor in 0.9.93 — post-install guard wire format converged onto InstallGuardError.toWire; adding a guard field now touches 8 → 2 points (pure refactor, zero wire-format semantic change)
|
|
659
671
|
|
|
660
672
|
- **Need (2026-10-16 architecture walk, friction point 3, the walk's top pick)**: the `InstallGuardError` → wire-format projection was hand-written verbatim in 5 places (host-api 409 payload / tools install+upgrade arms / cli install+upgrade arms) plus one more assembly in the client — adding a guard field meant touching 8 points (class + 5 projections + client read + tests); the later addition of `repairBasis` had already demonstrated the recurrence once.
|
package/lib/client.js
CHANGED
|
@@ -2005,6 +2005,53 @@ var init_install_cmd = __esm({
|
|
|
2005
2005
|
}
|
|
2006
2006
|
});
|
|
2007
2007
|
|
|
2008
|
+
// src/client/op-outcome.js
|
|
2009
|
+
var op_outcome_exports = {};
|
|
2010
|
+
__export(op_outcome_exports, {
|
|
2011
|
+
createOpOutcome: () => createOpOutcome
|
|
2012
|
+
});
|
|
2013
|
+
function createOpOutcome(lookup2) {
|
|
2014
|
+
const guardBlockedText = (guard) => [
|
|
2015
|
+
lookup2("guard.blocked"),
|
|
2016
|
+
`${lookup2("guard.compstatus")}: ${guard.compensation?.status || "\u2014"}\uFF08${guard.compensation?.note || ""}\uFF09`,
|
|
2017
|
+
guard.repairBasis ? `${lookup2("guard.repairbasis")}: ${guard.repairBasis}` : null,
|
|
2018
|
+
lookup2("guard.noforce"),
|
|
2019
|
+
guard.restartSafe ? lookup2("guard.restartsafenow") : lookup2("guard.restartunsafe")
|
|
2020
|
+
].filter(Boolean).join(" | ");
|
|
2021
|
+
const buildsSuffix = (res) => res.buildApprovals && res.buildApprovals.length ? lookup2("notify.builds", { names: res.buildApprovals.join(", ") }) : res.fallbackAllBuilds ? lookup2("notify.builds.fallback") : "";
|
|
2022
|
+
const upgradeOkText = (res) => {
|
|
2023
|
+
const note = upgradeNotify(res.activation);
|
|
2024
|
+
return {
|
|
2025
|
+
needsRestart: note.needsRestart,
|
|
2026
|
+
text: lookup2("notify.upgraded", {
|
|
2027
|
+
pkg: res.pkg,
|
|
2028
|
+
from: res.fromVersion ? `v${res.fromVersion}` : "\u2014",
|
|
2029
|
+
to: res.version ? `v${res.version}` : res.sha ? res.sha.slice(0, 7) : "latest"
|
|
2030
|
+
}) + buildsSuffix(res) + (note.suffixKey ? lookup2(note.suffixKey) : "")
|
|
2031
|
+
};
|
|
2032
|
+
};
|
|
2033
|
+
const mutationFailed = (e, { verb, target }) => {
|
|
2034
|
+
if (e && e.opSuperseded) return { kind: "ok", text: lookup2("op.superseded.note", { target }) };
|
|
2035
|
+
if (e && e.guard) return { kind: "err", text: guardBlockedText(e.guard) };
|
|
2036
|
+
return { kind: "err", text: lookup2(PLAIN_FAIL_KEY[verb], { err: e && e.message || e }) };
|
|
2037
|
+
};
|
|
2038
|
+
return { guardBlockedText, buildsSuffix, upgradeOkText, mutationFailed };
|
|
2039
|
+
}
|
|
2040
|
+
var PLAIN_FAIL_KEY;
|
|
2041
|
+
var init_op_outcome = __esm({
|
|
2042
|
+
"src/client/op-outcome.js"() {
|
|
2043
|
+
"use strict";
|
|
2044
|
+
init_operations();
|
|
2045
|
+
PLAIN_FAIL_KEY = {
|
|
2046
|
+
install: "failed.install",
|
|
2047
|
+
upgrade: "failed.upgrade",
|
|
2048
|
+
uninstall: "failed.uninstall",
|
|
2049
|
+
toggle: "toggle.failed",
|
|
2050
|
+
self: "notify.selfupgraded.failed"
|
|
2051
|
+
};
|
|
2052
|
+
}
|
|
2053
|
+
});
|
|
2054
|
+
|
|
2008
2055
|
// src/client/main.jsx
|
|
2009
2056
|
var React = require("react");
|
|
2010
2057
|
var rd = require("react-dom");
|
|
@@ -2028,11 +2075,12 @@ var { pickPayload: pickPayload2, parseToolArgs: parseToolArgs2 } = (init_tool_vi
|
|
|
2028
2075
|
var { RESTART_POLL_MS: RESTART_POLL_MS2, RESTART_DEADLINE_MS: RESTART_DEADLINE_MS2, nextRestartWait: nextRestartWait2, isAmbiguousRestartRequestError: isAmbiguousRestartRequestError2 } = (init_restart_wait(), __toCommonJS(restart_wait_exports));
|
|
2029
2076
|
var { refreshAfterMutation: refreshAfterMutation2 } = (init_view_refresh(), __toCommonJS(view_refresh_exports));
|
|
2030
2077
|
var { applyInstalledUpdates: applyInstalledUpdates2, installedUpdateStats: installedUpdateStats2 } = (init_installed_updates(), __toCommonJS(installed_updates_exports));
|
|
2031
|
-
var { createOperationsStore: createOperationsStore2, restoreRecords: restoreRecords2, createOpsPump: createOpsPump2, opAppliesTo: opAppliesTo2, TERMINAL_CLEARABLE: TERMINAL_CLEARABLE2,
|
|
2078
|
+
var { createOperationsStore: createOperationsStore2, restoreRecords: restoreRecords2, createOpsPump: createOpsPump2, opAppliesTo: opAppliesTo2, TERMINAL_CLEARABLE: TERMINAL_CLEARABLE2, REGISTRY_DEFAULT_TARGET: REGISTRY_DEFAULT_TARGET2 } = (init_operations(), __toCommonJS(operations_exports));
|
|
2032
2079
|
var { groupOpsByDay: groupOpsByDay2, opRowVm: opRowVm2, opUpgradeMeta: opUpgradeMeta2, canRemoveRow: canRemoveRow2, channelLabelKey: channelLabelKey2 } = (init_operations_view(), __toCommonJS(operations_view_exports));
|
|
2033
2080
|
var { createFavoritesStore: createFavoritesStore2, partitionStale: partitionStale2 } = (init_favorites(), __toCommonJS(favorites_exports));
|
|
2034
2081
|
var { readSelfCheckCache: readSelfCheckCache2, writeSelfCheckCache: writeSelfCheckCache2, clearSelfCheckCache: clearSelfCheckCache2, deriveChipState: deriveChipState2 } = (init_self_check(), __toCommonJS(self_check_exports));
|
|
2035
2082
|
var { shouldShowInstallCmd: shouldShowInstallCmd2 } = (init_install_cmd(), __toCommonJS(install_cmd_exports));
|
|
2083
|
+
var { createOpOutcome: createOpOutcome2 } = (init_op_outcome(), __toCommonJS(op_outcome_exports));
|
|
2036
2084
|
var ZH = {
|
|
2037
2085
|
"market.title": "\u63D2\u4EF6\u5E02\u573A",
|
|
2038
2086
|
"tab.market": "\u5E02\u573A",
|
|
@@ -2645,6 +2693,7 @@ function lookup(key, params) {
|
|
|
2645
2693
|
const dict = browserLang() === "en" ? EN : ZH;
|
|
2646
2694
|
return interpolate(dict[key] ?? ZH[key] ?? key, params);
|
|
2647
2695
|
}
|
|
2696
|
+
var oc = createOpOutcome2(lookup);
|
|
2648
2697
|
function communityLabels(data) {
|
|
2649
2698
|
const c = data && data.community && typeof data.community === "object" ? data.community : {};
|
|
2650
2699
|
const zh = c.categoryLabels && typeof c.categoryLabels === "object" ? c.categoryLabels : {};
|
|
@@ -4079,7 +4128,7 @@ function MarketTab({ notify, markets, onMutation, ops, favorites, profileKind, o
|
|
|
4079
4128
|
h(SearchBox, { key: zone, placeholder: lookup("search.ph"), initial: query.query, onCommit: (v) => updateQuery(v ? { query: v, category: null } : { query: v }) })
|
|
4080
4129
|
) : null;
|
|
4081
4130
|
const zoneBar = zoneChips3;
|
|
4082
|
-
const installResultText = (res) => lookup("notify.installed", { pkg: res.pkg, version: res.version ? ` v${res.version}` : "" }) +
|
|
4131
|
+
const installResultText = (res) => lookup("notify.installed", { pkg: res.pkg, version: res.version ? ` v${res.version}` : "" }) + oc.buildsSuffix(res) + (res.bundleWarning === "no-patch-layer" ? lookup("notify.bundlewarning") : "");
|
|
4083
4132
|
const installDone = (it2, res) => {
|
|
4084
4133
|
notify({ kind: "ok", needsRestart: true, text: installResultText(res) });
|
|
4085
4134
|
setInstallNote({ id: it2.id, kind: "ok", text: installResultText(res) });
|
|
@@ -4099,25 +4148,12 @@ function MarketTab({ notify, markets, onMutation, ops, favorites, profileKind, o
|
|
|
4099
4148
|
installDone(it, res);
|
|
4100
4149
|
await (onMutation ? onMutation() : reload(false));
|
|
4101
4150
|
} catch (e) {
|
|
4102
|
-
if (e && e.
|
|
4103
|
-
notify({ kind: "ok", text: lookup("op.superseded.note", { target: it.name }) });
|
|
4104
|
-
setInstallNote({ id: it.id, kind: "hint", text: lookup("op.superseded.note", { target: it.name }) });
|
|
4105
|
-
} else if (e && e.guard) {
|
|
4106
|
-
const guardText = [
|
|
4107
|
-
lookup("guard.blocked"),
|
|
4108
|
-
`${lookup("guard.compstatus")}: ${e.guard.compensation?.status || "\u2014"}\uFF08${e.guard.compensation?.note || ""}\uFF09`,
|
|
4109
|
-
e.guard.repairBasis ? `${lookup("guard.repairbasis")}: ${e.guard.repairBasis}` : null,
|
|
4110
|
-
lookup("guard.noforce"),
|
|
4111
|
-
e.guard.restartSafe ? lookup("guard.restartsafenow") : lookup("guard.restartunsafe")
|
|
4112
|
-
].filter(Boolean).join(" | ");
|
|
4113
|
-
notify({ kind: "err", text: guardText });
|
|
4114
|
-
setInstallNote({ id: it.id, kind: "err", text: guardText });
|
|
4115
|
-
} else if (e && e.issue) {
|
|
4151
|
+
if (e && e.issue) {
|
|
4116
4152
|
setCompatConfirm({ it, version, issue: e.issue, opId: e.opId });
|
|
4117
4153
|
} else {
|
|
4118
|
-
const
|
|
4119
|
-
notify({ kind:
|
|
4120
|
-
setInstallNote({ id: it.id, kind: "err", text:
|
|
4154
|
+
const m = oc.mutationFailed(e, { verb: "install", target: it.name });
|
|
4155
|
+
notify({ kind: m.kind, text: m.text });
|
|
4156
|
+
setInstallNote({ id: it.id, kind: m.kind === "ok" ? "hint" : "err", text: m.text });
|
|
4121
4157
|
}
|
|
4122
4158
|
}
|
|
4123
4159
|
};
|
|
@@ -4128,31 +4164,14 @@ function MarketTab({ notify, markets, onMutation, ops, favorites, profileKind, o
|
|
|
4128
4164
|
const m = opUpgradeMeta2(r);
|
|
4129
4165
|
return m ? { ...r, opMeta: m } : r;
|
|
4130
4166
|
});
|
|
4131
|
-
const
|
|
4132
|
-
|
|
4133
|
-
|
|
4134
|
-
setInstallNote({ id: it.id, kind: "ok", text });
|
|
4167
|
+
const ok = oc.upgradeOkText(res);
|
|
4168
|
+
notify({ kind: "ok", needsRestart: ok.needsRestart, text: ok.text });
|
|
4169
|
+
setInstallNote({ id: it.id, kind: "ok", text: ok.text });
|
|
4135
4170
|
await (onMutation ? onMutation() : void 0);
|
|
4136
4171
|
} catch (e) {
|
|
4137
|
-
|
|
4138
|
-
|
|
4139
|
-
|
|
4140
|
-
setInstallNote({ id: it.id, kind: "hint", text: sup });
|
|
4141
|
-
} else if (e && e.guard) {
|
|
4142
|
-
const guardText = [
|
|
4143
|
-
lookup("guard.blocked"),
|
|
4144
|
-
`${lookup("guard.compstatus")}: ${e.guard.compensation?.status || "\u2014"}\uFF08${e.guard.compensation?.note || ""}\uFF09`,
|
|
4145
|
-
e.guard.repairBasis ? `${lookup("guard.repairbasis")}: ${e.guard.repairBasis}` : null,
|
|
4146
|
-
lookup("guard.noforce"),
|
|
4147
|
-
e.guard.restartSafe ? lookup("guard.restartsafenow") : lookup("guard.restartunsafe")
|
|
4148
|
-
].filter(Boolean).join(" | ");
|
|
4149
|
-
notify({ kind: "err", text: guardText });
|
|
4150
|
-
setInstallNote({ id: it.id, kind: "err", text: guardText });
|
|
4151
|
-
} else {
|
|
4152
|
-
const failText = lookup("failed.upgrade", { err: e && e.message || e });
|
|
4153
|
-
notify({ kind: "err", text: failText });
|
|
4154
|
-
setInstallNote({ id: it.id, kind: "err", text: failText });
|
|
4155
|
-
}
|
|
4172
|
+
const m = oc.mutationFailed(e, { verb: "upgrade", target: it.installedPkg });
|
|
4173
|
+
notify({ kind: m.kind, text: m.text });
|
|
4174
|
+
setInstallNote({ id: it.id, kind: m.kind === "ok" ? "hint" : "err", text: m.text });
|
|
4156
4175
|
}
|
|
4157
4176
|
};
|
|
4158
4177
|
const CompatDialog = compatConfirm ? h(
|
|
@@ -4510,11 +4529,8 @@ function InstalledTab({ notify, installed, updates, onMutation, ops }) {
|
|
|
4510
4529
|
notify({ kind: "ok", needsRestart: note.needsRestart, text: lookup(note.textKey, note.params) + extra });
|
|
4511
4530
|
await reload();
|
|
4512
4531
|
} catch (e) {
|
|
4513
|
-
|
|
4514
|
-
|
|
4515
|
-
} else {
|
|
4516
|
-
notify({ kind: "err", text: lookup("toggle.failed", { err: e && e.message || e }) });
|
|
4517
|
-
}
|
|
4532
|
+
const m = oc.mutationFailed(e, { verb: "toggle", target: it.pkg });
|
|
4533
|
+
notify({ kind: m.kind, text: m.text });
|
|
4518
4534
|
} finally {
|
|
4519
4535
|
setPendingToggles((m) => {
|
|
4520
4536
|
if (!(it.pkg in m)) return m;
|
|
@@ -4533,11 +4549,8 @@ function InstalledTab({ notify, installed, updates, onMutation, ops }) {
|
|
|
4533
4549
|
});
|
|
4534
4550
|
await (onMutation ? onMutation() : reload());
|
|
4535
4551
|
} catch (e) {
|
|
4536
|
-
|
|
4537
|
-
|
|
4538
|
-
} else {
|
|
4539
|
-
notify({ kind: "err", text: lookup("failed.uninstall", { err: e && e.message || e }) });
|
|
4540
|
-
}
|
|
4552
|
+
const m = oc.mutationFailed(e, { verb: "uninstall", target: it.pkg });
|
|
4553
|
+
notify({ kind: m.kind, text: m.text });
|
|
4541
4554
|
}
|
|
4542
4555
|
};
|
|
4543
4556
|
const doUpgrade = async (it) => {
|
|
@@ -4547,34 +4560,12 @@ function InstalledTab({ notify, installed, updates, onMutation, ops }) {
|
|
|
4547
4560
|
const m = opUpgradeMeta2(r);
|
|
4548
4561
|
return m ? { ...r, opMeta: m } : r;
|
|
4549
4562
|
});
|
|
4550
|
-
const
|
|
4551
|
-
notify({
|
|
4552
|
-
kind: "ok",
|
|
4553
|
-
needsRestart: note.needsRestart,
|
|
4554
|
-
text: lookup("notify.upgraded", {
|
|
4555
|
-
pkg: res.pkg,
|
|
4556
|
-
from: res.fromVersion ? `v${res.fromVersion}` : "\u2014",
|
|
4557
|
-
to: res.version ? `v${res.version}` : res.sha ? res.sha.slice(0, 7) : "latest"
|
|
4558
|
-
}) + (res.buildApprovals && res.buildApprovals.length ? lookup("notify.builds", { names: res.buildApprovals.join(", ") }) : res.fallbackAllBuilds ? lookup("notify.builds.fallback") : "") + (note.suffixKey ? lookup(note.suffixKey) : "")
|
|
4559
|
-
});
|
|
4563
|
+
const ok = oc.upgradeOkText(res);
|
|
4564
|
+
notify({ kind: "ok", needsRestart: ok.needsRestart, text: ok.text });
|
|
4560
4565
|
await (onMutation ? onMutation() : reload());
|
|
4561
4566
|
} catch (e) {
|
|
4562
|
-
|
|
4563
|
-
|
|
4564
|
-
kind: "err",
|
|
4565
|
-
text: [
|
|
4566
|
-
lookup("guard.blocked"),
|
|
4567
|
-
`${lookup("guard.compstatus")}: ${e.guard.compensation?.status || "\u2014"}\uFF08${e.guard.compensation?.note || ""}\uFF09`,
|
|
4568
|
-
e.guard.repairBasis ? `${lookup("guard.repairbasis")}: ${e.guard.repairBasis}` : null,
|
|
4569
|
-
lookup("guard.noforce"),
|
|
4570
|
-
e.guard.restartSafe ? lookup("guard.restartsafenow") : lookup("guard.restartunsafe")
|
|
4571
|
-
].filter(Boolean).join(" | ")
|
|
4572
|
-
});
|
|
4573
|
-
} else if (e && e.opSuperseded) {
|
|
4574
|
-
notify({ kind: "ok", text: lookup("op.superseded.note", { target: it.pkg }) });
|
|
4575
|
-
} else {
|
|
4576
|
-
notify({ kind: "err", text: lookup("failed.upgrade", { err: e && e.message || e }) });
|
|
4577
|
-
}
|
|
4567
|
+
const m = oc.mutationFailed(e, { verb: "upgrade", target: it.pkg });
|
|
4568
|
+
notify({ kind: m.kind, text: m.text });
|
|
4578
4569
|
}
|
|
4579
4570
|
};
|
|
4580
4571
|
if (loading && !data) return h("div", { className: "dshm-empty" }, lookup("installed.loading"), Spin());
|
|
@@ -5211,13 +5202,14 @@ function DshmVersionChip({ version, notify, initialCheck, ops }) {
|
|
|
5211
5202
|
notify({
|
|
5212
5203
|
kind: "ok",
|
|
5213
5204
|
needsRestart: true,
|
|
5214
|
-
text: lookup("notify.selfupgraded", { from: version, to: res.version || "latest" }) +
|
|
5205
|
+
text: lookup("notify.selfupgraded", { from: version, to: res.version || "latest" }) + oc.buildsSuffix(res)
|
|
5215
5206
|
});
|
|
5216
5207
|
} catch (e) {
|
|
5217
5208
|
if (e && e.unsupported) {
|
|
5218
5209
|
notify({ kind: "info", text: e.unsupported.guidance || e && e.message || String(e) });
|
|
5219
5210
|
} else {
|
|
5220
|
-
|
|
5211
|
+
const m = oc.mutationFailed(e, { verb: "self", target: "dsh-m" });
|
|
5212
|
+
notify({ kind: m.kind, text: m.text });
|
|
5221
5213
|
}
|
|
5222
5214
|
} finally {
|
|
5223
5215
|
setBusy(false);
|
|
@@ -40,6 +40,7 @@ import { setLivePluginDisabled } from './live-plugin.js';
|
|
|
40
40
|
import { warmPackumentFor } from './packument.js';
|
|
41
41
|
import { syncNpmmirrorPackage } from './npm-route.js';
|
|
42
42
|
import { clearAllLatestCache } from './latest-cache.js';
|
|
43
|
+
import { parse as yamlParse } from 'yaml';
|
|
43
44
|
import { assertNpmIntegrity, atomicWriteFile, readPnpmLockIntegrity, readPnpmLockOverrides, restoreSnapshots, snapshotFiles, verifySnapshots, } from './npm-integrity.js';
|
|
44
45
|
import { isSafePluginTarget, makeDshRunner, removePatchedDependencyEntries, PNPM_OUTCOME_CODES, } from './dsh-cli.js';
|
|
45
46
|
export const TX_HEAL_CODES = [
|
|
@@ -811,6 +812,25 @@ async function uninstall(req, d, snapshots) {
|
|
|
811
812
|
}
|
|
812
813
|
}
|
|
813
814
|
// ---------- compensate-install 门(M2 Task 3:专用补偿事务,四边界见 DESIGN §3) ----------
|
|
815
|
+
/** 解析 pnpm-workspace.yaml 的 patchedDependencies 键集合(0.9.94 D9)。
|
|
816
|
+
* 文本不可解析(坏 YAML / 重复键)→ null(保守,不抛)——调用方据此走退化或 fail-closed;
|
|
817
|
+
* 空值/缺省(parse 得 null/undefined)与数组形态一律按空集处理(本机实证 parse('') === null;
|
|
818
|
+
* 防 typeof null === 'object' 朴素实现令 Object.keys(null) 抛错走 INTERNAL_ERROR——评审 N5)。 */
|
|
819
|
+
function workspacePatchedKeys(text) {
|
|
820
|
+
let doc;
|
|
821
|
+
try {
|
|
822
|
+
doc = yamlParse(text);
|
|
823
|
+
}
|
|
824
|
+
catch {
|
|
825
|
+
return null;
|
|
826
|
+
}
|
|
827
|
+
if (doc === null || typeof doc !== 'object' || Array.isArray(doc))
|
|
828
|
+
return new Set();
|
|
829
|
+
const patched = doc.patchedDependencies;
|
|
830
|
+
if (patched === null || typeof patched !== 'object' || Array.isArray(patched))
|
|
831
|
+
return new Set();
|
|
832
|
+
return new Set(Object.keys(patched));
|
|
833
|
+
}
|
|
814
834
|
/** patchMapping 结构化双落点恢复:workspace(pnpm-workspace.yaml patchedDependencies)与 manifest(pnpm.patchedDependencies)。 */
|
|
815
835
|
async function restorePatchMapping(profileDir, mapping, heal) {
|
|
816
836
|
const wsFile = join(profileDir, 'pnpm-workspace.yaml');
|
|
@@ -826,9 +846,13 @@ async function restorePatchMapping(profileDir, mapping, heal) {
|
|
|
826
846
|
if (!lines.some((l) => l.trim() === 'patchedDependencies:')) {
|
|
827
847
|
lines.push('patchedDependencies:');
|
|
828
848
|
}
|
|
849
|
+
// 0.9.94 D9:解析去重——同键任意引号形态(pnpm 的单引号/无引号书写)在场即跳过,杜绝对重复键文件的自造;
|
|
850
|
+
// 解析失败(坏 YAML/重复键)退化为今日逐字判重(保守不收紧)
|
|
851
|
+
const existingKeys = workspacePatchedKeys(text);
|
|
829
852
|
for (const entry of mapping.workspace) {
|
|
830
853
|
const line = ` ${JSON.stringify(entry.key)}: ${entry.patchPath}`;
|
|
831
|
-
|
|
854
|
+
const present = existingKeys !== null ? existingKeys.has(entry.key) : lines.includes(line);
|
|
855
|
+
if (!present)
|
|
832
856
|
lines.push(line);
|
|
833
857
|
}
|
|
834
858
|
await atomicWriteFile(wsFile, Buffer.from(lines.join('\n'), 'utf8'));
|
|
@@ -860,8 +884,13 @@ async function verifyPatchMapping(profileDir, mapping) {
|
|
|
860
884
|
if (mapping.workspace.length > 0) {
|
|
861
885
|
try {
|
|
862
886
|
const text = await readFile(join(profileDir, 'pnpm-workspace.yaml'), 'utf8');
|
|
887
|
+
// 0.9.94 D9:yaml 键成员判定替代双引号子串扫描——单引号/无引号在场形态不再被误判「复读缺失」;
|
|
888
|
+
// 解析失败(坏 YAML/重复键)fail-closed 报「复读失败」(与 manifest 支 JSON 解析失败同款纪律)
|
|
889
|
+
const keys = workspacePatchedKeys(text);
|
|
890
|
+
if (keys === null)
|
|
891
|
+
return 'pnpm-workspace.yaml 复读失败:YAML 无法解析';
|
|
863
892
|
for (const entry of mapping.workspace) {
|
|
864
|
-
if (!
|
|
893
|
+
if (!keys.has(entry.key))
|
|
865
894
|
return `workspace 补丁映射复读缺失:${entry.key}`;
|
|
866
895
|
}
|
|
867
896
|
}
|