dsh-oc-desktop 0.3.8 → 0.3.9

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.
@@ -122,6 +122,94 @@ function rangeFloor(range) {
122
122
  return best || '0.0.0';
123
123
  }
124
124
 
125
+ // ---------- 范围交集判定(rangeOverlap) ----------
126
+ // 判断两个 semver 范围是否有公共版本(用于引擎 dep 范围 vs 插件 peer 范围)。
127
+ // 避免 rangeFloor 下界误判:引擎 ^4.0.1 实际会装到 4.2.0+,插件要求 ^4.2.0 仍兼容。
128
+ // 范围 → 区间集合(|| 拆并,空格条件取交),再判任意区间交集非空。
129
+ // 区间 {lo, loIn, hi, hiIn}:lo/hi 为版本字符串或 null(null-lo=-∞,null-hi=+∞),In 标记闭开。
130
+ function cmpV(a, b, nullIs) {
131
+ if (a === null && b === null) return 0;
132
+ if (a === null) return nullIs === 1 ? 1 : -1;
133
+ if (b === null) return nullIs === 1 ? -1 : 1;
134
+ return semverCompare(a, b);
135
+ }
136
+ function singleToIntervals(cond) {
137
+ const m = cond.match(/^(>=|<=|>|<|=|~|\^)?(.+)$/);
138
+ const op = m[1] || '=';
139
+ const raw = (m[2] || '').trim();
140
+ if (!op && (raw === '*' || raw === 'x' || raw === 'X')) return [{ lo: null, loIn: true, hi: null, hiIn: false }];
141
+ if (/[xX*]/.test(raw)) {
142
+ const parts = raw.replace(/[xX*]/g, '0').split('.');
143
+ const lo = parts.join('.');
144
+ const hi = parts.length === 1 ? `${Number(parts[0]) + 1}.0.0` : `${parts[0]}.${Number(parts[1]) + 1}.0`;
145
+ return [{ lo, loIn: true, hi, hiIn: false }];
146
+ }
147
+ const pv = parseVersion(raw);
148
+ if (!pv) return [];
149
+ switch (op) {
150
+ case '=': return [{ lo: raw, loIn: true, hi: raw, hiIn: true }];
151
+ case '>': return [{ lo: raw, loIn: false, hi: null, hiIn: false }];
152
+ case '>=': return [{ lo: raw, loIn: true, hi: null, hiIn: false }];
153
+ case '<': return [{ lo: null, loIn: true, hi: raw, hiIn: false }];
154
+ case '<=': return [{ lo: null, loIn: true, hi: raw, hiIn: true }];
155
+ case '~': {
156
+ const parts = raw.split('.');
157
+ const hi = parts.length >= 2 ? `${pv.num[0]}.${pv.num[1] + 1}.0` : `${pv.num[0] + 1}.0.0`;
158
+ return [{ lo: raw, loIn: true, hi, hiIn: false }];
159
+ }
160
+ case '^': {
161
+ let hi;
162
+ if (pv.num[0] > 0) hi = `${pv.num[0] + 1}.0.0`;
163
+ else if (pv.num[1] > 0) hi = `0.${pv.num[1] + 1}.0`;
164
+ else hi = `0.0.${pv.num[2] + 1}`;
165
+ return [{ lo: raw, loIn: true, hi, hiIn: false }];
166
+ }
167
+ }
168
+ return [];
169
+ }
170
+ function intersectIntervals(a, b) {
171
+ const lo = cmpV(a.lo, b.lo, -1) > 0 ? { v: a.lo, in: a.loIn } : { v: b.lo, in: b.loIn };
172
+ const hi = cmpV(a.hi, b.hi, 1) < 0 ? { v: a.hi, in: a.hiIn } : { v: b.hi, in: b.hiIn };
173
+ const c = cmpV(lo.v, hi.v, 1);
174
+ if (c < 0) return { lo: lo.v, loIn: lo.in, hi: hi.v, hiIn: hi.in };
175
+ if (c > 0) return null;
176
+ return lo.in && hi.in ? { lo: lo.v, loIn: true, hi: hi.v, hiIn: true } : null;
177
+ }
178
+ function altToIntervals(alt) {
179
+ const conds = alt.split(/\s+/).filter(Boolean);
180
+ let result = [{ lo: null, loIn: true, hi: null, hiIn: false }];
181
+ for (const c of conds) {
182
+ const ivs = singleToIntervals(c);
183
+ if (ivs.length === 0) return [];
184
+ const next = [];
185
+ for (const a of result) for (const b of ivs) {
186
+ const iv = intersectIntervals(a, b);
187
+ if (iv) next.push(iv);
188
+ }
189
+ result = next;
190
+ if (result.length === 0) return result;
191
+ }
192
+ return result;
193
+ }
194
+ function rangeToIntervals(range) {
195
+ if (range == null || String(range).trim() === '') return [{ lo: null, loIn: true, hi: null, hiIn: false }];
196
+ const out = [];
197
+ for (const alt of String(range).trim().split(/\s*\|\|\s*/)) {
198
+ const t = alt.trim();
199
+ if (!t || t === '*' || t === 'x' || t === 'X') { out.push({ lo: null, loIn: true, hi: null, hiIn: false }); continue; }
200
+ const h = t.match(/^(\S+)\s+-\s+(\S+)$/);
201
+ if (h) { out.push({ lo: h[1], loIn: true, hi: h[2], hiIn: true }); continue; }
202
+ for (const iv of altToIntervals(t)) out.push(iv);
203
+ }
204
+ return out;
205
+ }
206
+ function rangeOverlap(rangeA, rangeB) {
207
+ const ia = rangeToIntervals(rangeA);
208
+ const ib = rangeToIntervals(rangeB);
209
+ for (const x of ia) for (const y of ib) if (intersectIntervals(x, y)) return true;
210
+ return false;
211
+ }
212
+
125
213
  // GET 一个 JSON(支持 http/https),带超时与体积上限。
126
214
  function fetchJson(url, timeoutMs = REQUEST_TIMEOUT_MS) {
127
215
  return new Promise((resolve, reject) => {
@@ -310,7 +398,8 @@ async function npmPackageVersions(pkg, registry = 'https://registry.npmjs.org')
310
398
 
311
399
  // 判定插件(peerDependencies)是否兼容指定引擎。
312
400
  // 对插件 peerDeps 中每个 @deepseek-ai/* 包:
313
- // - 引擎 deps 含该包 → 用引擎 dep 范围的下界版本测试插件范围
401
+ // - 引擎 deps 含该包 → 比较引擎 dep 范围与插件范围是否有交集(rangeOverlap)。
402
+ // 不用 rangeFloor 下界(引擎 ^4.0.1 实际装到 4.2.0+,插件 ^4.2.0 仍兼容)。
314
403
  // - 引擎不含 → 通过 fetchVersions(pkg) 查 npm 该包全量版本,存在满足插件范围的版本即兼容
315
404
  // (这些 dsh-client-* 包用 next tag 发 rc 版,latest tag 常停在早期版,不能用 /latest 判断)
316
405
  // 全部满足 → 兼容。fetchVersions: async (pkg) => [版本字符串,...]
@@ -320,7 +409,7 @@ async function checkEngineCompat(pluginPeerDeps, engineDeps, fetchVersions) {
320
409
  for (const k of scope) {
321
410
  const needRange = pluginPeerDeps[k];
322
411
  if (engineDeps && engineDeps[k]) {
323
- if (!semverRangeMatch(needRange, rangeFloor(engineDeps[k]))) return false;
412
+ if (!rangeOverlap(needRange, engineDeps[k])) return false;
324
413
  continue;
325
414
  }
326
415
  if (typeof fetchVersions !== 'function') return false;
@@ -332,7 +421,7 @@ async function checkEngineCompat(pluginPeerDeps, engineDeps, fetchVersions) {
332
421
  }
333
422
 
334
423
  module.exports = {
335
- parseVersion, semverCompare, semverRangeMatch, rangeFloor, fetchJson, checkForUpdates,
424
+ parseVersion, semverCompare, semverRangeMatch, rangeFloor, rangeOverlap, fetchJson, checkForUpdates,
336
425
  hashFile, diffLocal, downloadFile, downloadUpdate, npmLatestVersion, npmPackageInfo,
337
426
  npmPackageVersions, checkNpmUpdates, checkEngineCompat,
338
427
  };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "dsh-oc-desktop",
3
3
  "productName": "DeepSeek Harness Desktop",
4
- "version": "0.3.8",
4
+ "version": "0.3.9",
5
5
  "description": "DeepSeek Harness 桌面端插件(Electron 启动器):自绘标题栏/托盘/通知/快捷键/崩溃自愈",
6
6
  "main": "launcher/main.js",
7
7
  "bin": {
@@ -3,7 +3,7 @@ import assert from "node:assert";
3
3
  import http from "node:http";
4
4
  import { createRequire } from "node:module";
5
5
  const require = createRequire(import.meta.url);
6
- const { npmLatestVersion, npmPackageVersions, checkNpmUpdates, checkEngineCompat, semverRangeMatch, rangeFloor, semverCompare, parseVersion } = require("../launcher/updater.js");
6
+ const { npmLatestVersion, npmPackageVersions, checkNpmUpdates, checkEngineCompat, semverRangeMatch, rangeFloor, rangeOverlap, semverCompare, parseVersion } = require("../launcher/updater.js");
7
7
 
8
8
  // 1. pure version compare logic
9
9
  assert.strictEqual(parseVersion("0.3.6").num.join("."), "0.3.6");
@@ -89,6 +89,9 @@ const fetchVersions = async (p) => (await npmPackageVersions(p, base));
89
89
  const engineDeps = { "@deepseek-ai/cordis": "^4.0.1" };
90
90
  assert.strictEqual(await checkEngineCompat({ "@deepseek-ai/cordis": "^4.0.1" }, engineDeps, fetchVersions), true);
91
91
  assert.strictEqual(await checkEngineCompat({ "@deepseek-ai/cordis": "^3.0.0" }, engineDeps, fetchVersions), false);
92
+ // engine range ^4.0.1 vs plugin ^4.2.0: overlap in [4.2.0,5.0.0) -> compatible (not rangeFloor false-negative)
93
+ assert.strictEqual(await checkEngineCompat({ "@deepseek-ai/cordis": "^4.2.0" }, engineDeps, fetchVersions), true);
94
+ assert.strictEqual(await checkEngineCompat({ "@deepseek-ai/cordis": "^5.0.0" }, engineDeps, fetchVersions), false);
92
95
  // latest tag is 0.0.1-rc.1 but the full version list has 0.1.0-rc.6 (next tag) — compatible
93
96
  assert.strictEqual(await checkEngineCompat({ "@deepseek-ai/cordis": "^4.0.1", "@deepseek-ai/dsh-client-locale": "^0.1.0-rc.6" }, engineDeps, fetchVersions), true);
94
97
  // engine-independent package with no satisfying version -> incompatible
@@ -96,5 +99,16 @@ assert.strictEqual(await checkEngineCompat({ "@deepseek-ai/cordis": "^4.0.1", "@
96
99
  // package not in engine deps but its versions satisfy the plugin range
97
100
  assert.strictEqual(await checkEngineCompat({ "@deepseek-ai/cordis": "^4.0.1" }, {}, fetchVersions), true);
98
101
 
102
+ // 4b. rangeOverlap: semver range intersection (engine dep range vs plugin peer range)
103
+ assert.strictEqual(rangeOverlap("^4.0.1", "^4.2.0"), true);
104
+ assert.strictEqual(rangeOverlap("^3.0.0", "^4.0.1"), false);
105
+ assert.strictEqual(rangeOverlap("^4.2.0", ">=4.0.0 <5.0.0"), true);
106
+ assert.strictEqual(rangeOverlap(">=4.0.0 <4.5.0", ">=4.5.0"), false);
107
+ assert.strictEqual(rangeOverlap("~4.0.1", "^4.0.1"), true);
108
+ assert.strictEqual(rangeOverlap("4.0.1", "^4.0.1"), true);
109
+ assert.strictEqual(rangeOverlap("^0.1.0-rc.6", "^0.1.1-rc.2"), true);
110
+ assert.strictEqual(rangeOverlap("1.2.x", ">=1.2.0 <1.3.0"), true);
111
+ assert.strictEqual(rangeOverlap("^4.0.1", "3.x || 4.x"), true);
112
+
99
113
  registry.close();
100
114
  console.log("updater tests passed");