@noob-stupid/dsh-plugin-console 0.5.20 → 0.5.21

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/lib/client.js CHANGED
@@ -5217,7 +5217,7 @@ setMessage(t("failed") + ":" + (data?.reason ?? "未知"));
5217
5217
  phase: done?.ok === true ? "done" : "error",
5218
5218
  text: parts.join(" · "),
5219
5219
  level: done?.ok === true ? null : "warn",
5220
- title: [done?.command, done?.stdout, raw].filter((s) => typeof s === "string" && s !== "").join("\n") || null,
5220
+ title: [done?.command, typeof done?.exitCode === "number" ? "exitCode=" + done.exitCode : null, done?.stdout, raw].filter((s) => typeof s === "string" && s !== "").join("\n") || null,
5221
5221
  closed: false,
5222
5222
  });
5223
5223
  },
@@ -34,6 +34,19 @@ const PROBE_TIMEOUT_MS = 8000
34
34
  const REPAIR_TIMEOUT_MS = 180000
35
35
  const DEP_SECTIONS = ['dependencies', 'devDependencies', 'optionalDependencies']
36
36
 
37
+ /**
38
+ * spec 是不是**非 registry 来源**(`link:` / `file:` / `workspace:` / `portal:` / URL / git / 别名)。
39
+ * 2026-09-27 改错(与 0.5.20 的写回规则配套):这类依赖**不经 registry 解析**,所以"registry 上 404"
40
+ * 对它们毫无信息量 —— 体检不能拿它判问题,重建更不能被它拦下。真实现场:desktop profile 的两个
41
+ * `@dsh-external/*` 按新规则钉成 `link:` 之后,体检仍按包名去 registry 探,报 `fetch-404` 并把重建
42
+ * **永久**挡在门外(`repair.applicable=false`),而 lock 其实完全一致。
43
+ * 与 `specSatisfiedBy` 共用同一份判据(唯一真源,避免两处各写一条正则)。
44
+ */
45
+ function isSourceSpec(spec) {
46
+ return typeof spec === 'string'
47
+ && /^(?:link|file|workspace|portal|npm|git\+|git:|github:|gitlab:|bitbucket:|https?:)/iu.test(spec.trim())
48
+ }
49
+
37
50
  /** 重建 lock 的 argv(纯函数,唯一产出点):只重写 lock,不碰 node_modules;
38
51
  * `--no-frozen-lockfile` 是必需的(我们的 pnpm env 带 CI=true,pnpm 在 CI 下默认 frozen-lockfile,
39
52
  * 不加它就只会得到 `ERR_PNPM_OUTDATED_LOCKFILE` —— 正是要修的那个症状)。
@@ -111,7 +124,7 @@ function parseLockImporters(lockText) {
111
124
  * · 非 registry 来源(link:/file:/git+/http…)→ 交给 specifier 相等判定,这里恒 true。 */
112
125
  function specSatisfiedBy(spec, version) {
113
126
  if (typeof spec !== 'string' || spec === '') return true
114
- if (/^(?:link|file|workspace|portal|npm|git\+|git:|github:|https?:)/iu.test(spec)) return true
127
+ if (isSourceSpec(spec)) return true
115
128
  if (typeof version !== 'string' || version === '') return false
116
129
  if (/^[\^~]?\d/u.test(spec) || /^(?:>=|>|=|<=|<)\s*\d/u.test(spec)) return semverRangeMatch(version, spec)
117
130
  return version === spec
@@ -151,7 +164,9 @@ function diffLockfile({ manifestText, lockText, installed = {} } = {}) {
151
164
  state = 'version-mismatch'
152
165
  }
153
166
  const onDisk = installed[dep.name] ?? null
154
- if (onDisk !== null && locked.version !== null && onDisk !== locked.version) {
167
+ // 来源型 spec(link:/file:/URL/git)没有"版本号相等"可比:按盘上版本号与 lock 里的
168
+ // `link:../../…` 比,必然"漂移"(假阳性)。两个方向都判来源(spec 或 lock 解析任一是来源)。
169
+ if (onDisk !== null && locked.version !== null && !isSourceSpec(dep.spec) && !isSourceSpec(locked.version) && onDisk !== locked.version) {
155
170
  drift.push({ name: dep.name, installed: onDisk, lockVersion: locked.version })
156
171
  }
157
172
  deps.push({ name: dep.name, spec: dep.spec, section: dep.section, lockSpecifier: locked.specifier, lockVersion: locked.version, state })
@@ -250,8 +265,10 @@ async function runLockfileCheck({
250
265
  const diff = diffLockfile({ manifestText: manifestText ?? '', lockText, installed })
251
266
  const manifest = readManifestDeps(manifestText ?? '')
252
267
 
253
- // ① 依赖能不能在 registry 上解析(404 要点名;不可达与 404 分开说)
254
- const probes = await Promise.all((manifest.ok ? manifest.deps : []).map(async (dep) => {
268
+ // ① 依赖能不能在 registry 上解析(404 要点名;不可达与 404 分开说)。
269
+ // **来源型 spec 跳过探测**(2026-09-27 改错):link:/file:/URL/git 本来就不经 registry 解析。
270
+ const sourceSpecDeps = (manifest.ok ? manifest.deps : []).filter((dep) => isSourceSpec(dep.spec)).map((dep) => dep.name)
271
+ const probes = await Promise.all((manifest.ok ? manifest.deps : []).filter((dep) => !isSourceSpec(dep.spec)).map(async (dep) => {
255
272
  const result = await probe(dep.name, probeList, {
256
273
  version: exactVersionOf(dep.spec),
257
274
  timeoutMs: PROBE_TIMEOUT_MS,
@@ -316,6 +333,8 @@ async function runLockfileCheck({
316
333
  lockfile: { present: diff.lockfilePresent, parsed: diff.lockfileParsed, entries: diff.deps.filter((d) => d.lockVersion !== null).length },
317
334
  problems,
318
335
  packages404: name404,
336
+ // 非 registry 来源的依赖(link:/file:/URL/git):明确列出来,说明"为什么没探测它们"
337
+ sourceLinked: sourceSpecDeps,
319
338
  outdated: {
320
339
  lockfileMissing: !diff.lockfilePresent,
321
340
  missing: diff.missing,
@@ -422,6 +441,7 @@ export {
422
441
  diffLockfile,
423
442
  exactVersionOf,
424
443
  freshReleases,
444
+ isSourceSpec,
425
445
  parseLockImporters,
426
446
  problemLines,
427
447
  probeVerdict,
@@ -177,9 +177,15 @@ async function runSuggestedAction({ body, profileDir, registries = [], deps = {}
177
177
  const manifests = { before, after, changed: before !== after, planned: pinned.spec, pinned: pinnedOk }
178
178
  const notes = []
179
179
  if (run.relaxedReleaseAge) notes.push('本机 pnpm 的 24h 供应链闸(minimumReleaseAge)拦下了这次 lock 校验,已**仅对这一条命令**追加 --config.minimumReleaseAge=0 重试一次(有安全代价,未写进任何配置文件)。')
180
- if (!run.ok && !manifests.pinned) notes.push('清单与 lock 都没改成功:见 stderr(原始输出已原样回显)。')
181
- // 只有"lock 里确实没有这条 link"时才说 lock 没对齐 —— 否则就是谎报(本次 pnpm 只是重复做了个空动作)
182
- if (!run.ok && manifests.pinned && !lockSynced) notes.push('清单已钉住(下次 pnpm 操作不会再 404),但 lock 没能对齐:见 stderr。')
180
+ // 三种"pnpm 那一步没跑成"的形态都如实记一句(2026-09-27 真机桌面端实例实测:corepack 在该进程里
181
+ // 解析不到 → pnpm 那一步失败,而清单/lock 其实已达成目标;只留 stderr 会让用户自己猜发生了什么)
182
+ if (!run.ok) {
183
+ notes.push(manifests.pinned
184
+ ? (lockSynced
185
+ ? `pnpm 那一步没跑完(exitCode=${run.exitCode},原始输出见 stderr):清单与 lock 已经是 link:,目标状态已达成。`
186
+ : `清单已钉住(下次 pnpm 操作不会再 404),但 lock 没能对齐(exitCode=${run.exitCode}):见 stderr。`)
187
+ : `清单与 lock 都没改成功(exitCode=${run.exitCode}):见 stderr(原始输出已原样回显)。`)
188
+ }
183
189
  return {
184
190
  ...base,
185
191
  // 只有"清单真的是 link: **且** lock 里也有这条 link"才算成功;只成一半时 partial=true 并如实说明
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@noob-stupid/dsh-plugin-console",
3
- "version": "0.5.20",
3
+ "version": "0.5.21",
4
4
  "description": "DSH 框架升级安全与插件升级门控:一键升级、失败自动回滚、升级后回滚上版、旧插件不适配自动禁用;内置多源插件市场为发现层,插件源全部可自定义,可指向公司内网私有源 / 私有索引 / 本地 Git 仓库,纯内网离线可用 | Framework upgrade safety & plugin version gating for DSH, with a customizable multi-source plugin market: point every source at internal mirrors or a local file:// repo for intranet-only, offline installs.",
5
5
  "repository": {
6
6
  "type": "git",