dsh-m 0.2.4 → 0.2.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.en.md +1 -1
- package/README.md +1 -1
- package/docs/DESIGN.md +1 -1
- package/lib/client.js +3 -2
- package/lib/core/market.js +17 -2
- package/package.json +1 -1
package/README.en.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
[](https://www.npmjs.com/package/dsh-m)
|
|
5
5
|
[](../../actions/workflows/registry.yml)
|
|
6
6
|
[](./LICENSE)
|
|
7
|
-
[](#faq)
|
|
8
8
|
|
|
9
9
|
English · [中文](./README.md)
|
|
10
10
|
|
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
[](https://www.npmjs.com/package/dsh-m)
|
|
5
5
|
[](../../actions/workflows/registry.yml)
|
|
6
6
|
[](./LICENSE)
|
|
7
|
-
[](#faq)
|
|
8
8
|
|
|
9
9
|
[English](./README.en.md) · 中文
|
|
10
10
|
|
package/docs/DESIGN.md
CHANGED
|
@@ -63,7 +63,7 @@
|
|
|
63
63
|
|
|
64
64
|
底层原语(本机实证):`dsh plugin --profile web add|remove|update`(转发 pnpm,作用于 `$DSH_HOME/profiles/web`)。**profile 的 `package.json` 就是唯一事实源**——不引入任何额外状态文件。
|
|
65
65
|
|
|
66
|
-
- **安装(npm 源)**:装最新版并以**精确版本锁定**(不用 `^` 范围;用户指定版本必须为精确 semver,经该精确版本 endpoint 查询)。安装前对 profile 的 `package.json` / `pnpm-lock.yaml` / `pnpm-workspace.yaml` 做**字节快照**;安装后核验 importer
|
|
66
|
+
- **安装(npm 源)**:装最新版并以**精确版本锁定**(不用 `^` 范围;用户指定版本必须为精确 semver,经该精确版本 endpoint 查询)。安装前对 profile 的 `package.json` / `pnpm-lock.yaml` / `pnpm-workspace.yaml` 做**字节快照**;安装后核验 importer 依赖为该精确版本(旧版 CLI 写入的 `^v`/`~v` 锚定 range 视为等价放行,2026-09-05 实机实证;精确性由紧随的 lockfile 解析与 integrity 兜底,其余 spec 仍 fail closed),并在 lockfile `packages` 条目中比对与 npm dist 一致的 `resolution.integrity`——缺失或不一致 **fail closed** 并执行 **best-effort dependency rollback**:原子恢复快照字节 + frozen 自愈阶梯(`pnpm install --frozen-lockfile` → 仍报 `CONFIG_MISMATCH` 时把 lockfile 记录的 overrides 对齐回 `package.json#pnpm.overrides` 再复验 → `CONFIG_MISMATCH` / `OUTDATED_LOCKFILE` 顽固失配才降级 `--no-frozen-lockfile` 重建并明确告知「lockfile 已重建」;全阶梯失败才报「可能需要人工修复」)。成功路径若发现安装链丢失了 manifest 顶层未知键(如 `pnpm.overrides`,2026-09-05 升级回滚事故),从快照找回并复验 frozen 一致性,输出带 `[dsh-m 自愈]` 报告。刚发布 ~1 分钟内的 `ERR_PNPM_NO_MATCHING_VERSION` 多为 packument CDN 滞后:退避重试 2 次(5s/15s),每次重试前拉一次完整 packument 预热。不声称 node_modules 与间接依赖已字节级回滚。
|
|
67
67
|
- **安装(GitHub 源)**:解析并**锁定 commit SHA**(`github:owner/repo#sha`),skillhub 同款。
|
|
68
68
|
- **已装识别**:读 profile `package.json` dependencies,与 registry 匹配 → 标注「市场安装」;不匹配的也列出,标注「非市场安装 / 来源未知」。卸载/升级对两类都可用。
|
|
69
69
|
- **卸载**:live-disable(先让 client bundle 下线,避免 404)→ 摘除该包在 profile 的补丁条目(`pnpm-workspace.yaml` 顶层 `patchedDependencies` 与 `package.json#pnpm.patchedDependencies`;依赖移除后残留条目会令 pnpm 以 `ERR_PNPM_UNUSED_PATCH` 整单失败,只精确匹配 `pkg` / `pkg@ver`,补丁文件本体保留并计入残留报告)→ `dsh plugin remove`。**不清理插件产生的数据/配置**,但把检测到的疑似残留路径(如 `~/.dsh/<plugin>.json`)列出报告。
|
package/lib/client.js
CHANGED
|
@@ -512,6 +512,7 @@ var CSS = `
|
|
|
512
512
|
.dshm-detail{margin-top:8px;border-top:1px dashed var(--dsw-alias-border-l2,#e5e7eb);padding-top:8px;display:flex;flex-direction:column;gap:6px;font-size:12px;color:var(--dsw-alias-label-secondary,#4b5563)}
|
|
513
513
|
.dshm-actions{display:flex;gap:6px;flex-wrap:wrap;margin-top:4px}
|
|
514
514
|
.dshm-banner{display:flex;align-items:center;gap:10px;padding:10px 14px;border-top:1px solid var(--dsw-alias-border-l2,#e5e7eb);background:var(--dsw-alias-state-warn-tertiary,#fffbeb);color:var(--dsw-alias-state-warn-primary,#b45309);font-size:12px}
|
|
515
|
+
.dshm-banner.err{background:rgba(239,68,68,.13);color:var(--dsw-alias-state-error-primary,#b91c1c)}
|
|
515
516
|
.dshm-banner .dshm-banner-text{flex:1;max-height:140px;overflow:auto;overscroll-behavior:contain;white-space:pre-wrap;word-break:break-word;line-height:18px}
|
|
516
517
|
.dshm-row{display:flex;align-items:center;gap:8px}
|
|
517
518
|
.dshm-kv{display:grid;grid-template-columns:96px 1fr;gap:6px 10px;font-size:12px;align-items:baseline}
|
|
@@ -1526,7 +1527,7 @@ function MarketPanel({ onClose }) {
|
|
|
1526
1527
|
}, [onClose]);
|
|
1527
1528
|
useEffect(() => {
|
|
1528
1529
|
if (!toast) return;
|
|
1529
|
-
const t = setTimeout(() => setToast(null), 6e3);
|
|
1530
|
+
const t = setTimeout(() => setToast(null), toast.kind === "err" ? 15e3 : 6e3);
|
|
1530
1531
|
return () => clearTimeout(t);
|
|
1531
1532
|
}, [toast]);
|
|
1532
1533
|
const notify = useCallback(({ kind, text, needsRestart }) => {
|
|
@@ -1574,7 +1575,7 @@ function MarketPanel({ onClose }) {
|
|
|
1574
1575
|
),
|
|
1575
1576
|
toast ? h(
|
|
1576
1577
|
"div",
|
|
1577
|
-
{ className:
|
|
1578
|
+
{ className: toast.kind === "err" ? "dshm-banner err" : "dshm-banner" },
|
|
1578
1579
|
h("span", { className: "dshm-banner-text" }, toast.text)
|
|
1579
1580
|
) : null,
|
|
1580
1581
|
banner ? h(RestartBanner, { note: banner.text, onDone: () => setBanner(null) }) : null
|
package/lib/core/market.js
CHANGED
|
@@ -555,6 +555,17 @@ async function addDshPluginWithRetry(spec, pkg, d, retryDelaysMs, timeoutMs, sig
|
|
|
555
555
|
}
|
|
556
556
|
}
|
|
557
557
|
}
|
|
558
|
+
/**
|
|
559
|
+
* 旧版 dsh CLI(save-prefix ^)安装后会把依赖写成 `^x.y.z` 而非精确版本
|
|
560
|
+
* (2026-09-05 实机实证:升级报「profile 依赖版本 ^0.2.5 与目标 0.2.5 不一致」)。
|
|
561
|
+
* 仅当 spec 恰好锚定在目标精确版本上(v 本身 / ^v / ~v)才放行;真正的精确性
|
|
562
|
+
* 不靠 manifest 字符串,而由紧随其后的 lockfile importer 精确解析 + integrity
|
|
563
|
+
* 比对保证——其它任何 spec 仍然 fail closed。
|
|
564
|
+
*/
|
|
565
|
+
function specAnchoredAtVersion(spec, version) {
|
|
566
|
+
const s = String(spec || '').trim();
|
|
567
|
+
return s === version || s === `^${version}` || s === `~${version}`;
|
|
568
|
+
}
|
|
558
569
|
/** 从 registry 收录条目安装(npm → 精确锁定最新版;github → 锁 HEAD SHA)。 */
|
|
559
570
|
export async function installFromRegistry(id, cfg = {}, opts = {}, deps) {
|
|
560
571
|
const loaded = await (deps?.loadRegistry ?? defaultLoadRegistry)(cfg, { namespace: opts.namespace ?? 'host' });
|
|
@@ -614,8 +625,12 @@ export async function installEntry(entry, cfg = {}, opts = {}, deps) {
|
|
|
614
625
|
const depsNow = await d.readProfileDeps(profileDir);
|
|
615
626
|
if (depsNow[pkg] === undefined)
|
|
616
627
|
throw new Error(`安装后未在 profile 依赖中找到 ${pkg}`);
|
|
617
|
-
if (depsNow[pkg] !== version)
|
|
618
|
-
|
|
628
|
+
if (depsNow[pkg] !== version) {
|
|
629
|
+
if (!specAnchoredAtVersion(depsNow[pkg], version)) {
|
|
630
|
+
throw new Error(`profile 依赖版本 ${depsNow[pkg]} 与目标 ${version} 不一致`);
|
|
631
|
+
}
|
|
632
|
+
healNotes.push(`安装链把依赖写成 range(${depsNow[pkg]},旧版 CLI save-prefix 行为);精确性由 lockfile integrity 校验继续保证`);
|
|
633
|
+
}
|
|
619
634
|
let lockText;
|
|
620
635
|
try {
|
|
621
636
|
lockText = await readFile(join(profileDir, 'pnpm-lock.yaml'), 'utf8');
|