dshmarket 1.66.25 → 1.66.26

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/check.js CHANGED
@@ -313,6 +313,33 @@ function parseSemver(value) {
313
313
  pre: m[4] === undefined ? [] : m[4].split('.'),
314
314
  };
315
315
  }
316
+ /**
317
+ * Parse a comparator's target the way node-semver does: a PARTIAL version in
318
+ * a range is zero-filled, so `<0.3` is `<0.3.0` and `>=2` is `>=2.0.0`.
319
+ *
320
+ * Without this, every prerelease floor written as a partial — the
321
+ * `@deepseek-ai/dsh-*` peers of a whole family of connector plugins say
322
+ * `>=0.1.5 <0.3`, `>=0.1.7-rc <2`, `>=0.2.0-rc.0 <0.3` — parsed as unknown,
323
+ * `satisfiesRange` answered null, and the gate stood aside: measured against
324
+ * the host's own evaluator over 699 live catalog manifests, those were the
325
+ * last 7 of 2796 (package, runtime) pairs where the host refuses a plugin
326
+ * and the market still promised a restart would apply it.
327
+ *
328
+ * A BARE partial (`1.2`) is a different thing in semver — an x-range that
329
+ * means `>=1.2.0 <1.3.0` — so it deliberately stays unknown here rather than
330
+ * being mistaken for the exact version `1.2.0`.
331
+ */
332
+ function parseComparatorTarget(target, op) {
333
+ const direct = parseSemver(target);
334
+ if (direct !== null)
335
+ return direct;
336
+ if (op === '')
337
+ return null;
338
+ const parts = target.trim().split('.');
339
+ if (parts.length > 3 || parts.some(part => !/^\d+$/u.test(part)))
340
+ return null;
341
+ return parseSemver([...parts, ...Array(3 - parts.length).fill('0')].join('.'));
342
+ }
316
343
  function comparePre(a, b) {
317
344
  const len = Math.max(a.length, b.length);
318
345
  for (let i = 0; i < len; i += 1) {
@@ -441,7 +468,7 @@ export function satisfiesRange(version, range, options = {}) {
441
468
  const m = /^(\^|~|>=|<=|>|<)?(.*)$/.exec(p);
442
469
  const op = m?.[1] ?? '';
443
470
  const target = (m?.[2] ?? '').trim();
444
- const tv = parseSemver(target);
471
+ const tv = parseComparatorTarget(target, op);
445
472
  if (tv === null)
446
473
  return null;
447
474
  const major = tv.major;
@@ -488,7 +515,7 @@ export function satisfiesRange(version, range, options = {}) {
488
515
  // prerelease admission gate. Otherwise an unknown protocol such as
489
516
  // `workspace:^` or `catalog:default` is misreported as a definite false
490
517
  // whenever the resolved version happens to carry a prerelease tag.
491
- if (parseSemver(target) === null)
518
+ if (parseComparatorTarget(target, m[1] ?? '') === null)
492
519
  return null;
493
520
  return { op: m[1] ?? '', target };
494
521
  };
package/lib/verify.js CHANGED
@@ -186,6 +186,19 @@ const isDshPeer = (peer) => peer === '@deepseek-ai/dsh' || peer.startsWith('@dee
186
186
  export function hostPeerGate(activeProfileDir, name, facts) {
187
187
  return peerGateAt(join(activeProfileDir, 'node_modules', name), name, facts);
188
188
  }
189
+ /**
190
+ * The unmet requirement, if any, and the remedy side it points at.
191
+ *
192
+ * Which side of the range the runtime sits on decides the remedy: below the
193
+ * floor, upgrading the host genuinely helps; above the cap (#757's own
194
+ * shape) no upgrade can satisfy it, and suggesting one points the wrong way
195
+ * (#758).
196
+ */
197
+ function unmetOf(peer, range, runtimeVersion) {
198
+ const floor = /[0-9]+\.[0-9]+\.[0-9]+[-+0-9A-Za-z.]*/.exec(range);
199
+ const hostTooOld = floor !== null && compareSemver(runtimeVersion, floor[0]) < 0;
200
+ return { peer, range, runtimeVersion, hostTooOld };
201
+ }
189
202
  /** Same prediction for a patch plugin resolved outside the profile's flat node_modules. */
190
203
  function peerGateAt(packageDir, name, facts) {
191
204
  if (facts.runtimeVersion === null)
@@ -201,22 +214,33 @@ function peerGateAt(packageDir, name, facts) {
201
214
  if (version !== '' && (facts.exemptions[`${name}@${version}`] ?? []).includes(facts.runtimeVersion)) {
202
215
  return null;
203
216
  }
217
+ // An own `engines.dsh` range decides ALONE and peerDependencies are then
218
+ // ignored — the host's rule, read from unmetRequirements in
219
+ // @deepseek-ai/dsh-app-boot, and the reason a gate that only read peers
220
+ // was too lenient on 62 of 2796 (package, runtime) pairs measured over the
221
+ // live catalog: `dsh-file-explorer-kit` (engines.dsh ^0.1.1) and
222
+ // `dsh-workspace-kit` (^0.1.2-rc.1) declare NO dsh peer at all, so the
223
+ // family that refuses them was invisible here and the card promised a
224
+ // restart the host would not honour. A non-string range throws inside the
225
+ // host's own evaluator; that is not a verdict this can predict, so the
226
+ // old answer stands (#757).
227
+ const engines = manifest.engines?.dsh;
228
+ if (engines !== undefined) {
229
+ if (typeof engines !== 'string')
230
+ return null;
231
+ const range = engines.trim();
232
+ // `workspace:` is substituted only for PEERS: the host counts it as an
233
+ // unmet range for engines, so an empty or invalid range stays unmet too.
234
+ const satisfied = range === '' ? false : satisfiesRange(facts.runtimeVersion, range, { includePrerelease: true });
235
+ return satisfied ? null : unmetOf('engines.dsh', range, facts.runtimeVersion);
236
+ }
204
237
  for (const [peer, requirement] of Object.entries(manifest.peerDependencies ?? {})) {
205
238
  if (!isDshPeer(peer) || typeof requirement !== 'string')
206
239
  continue;
207
240
  const range = requirement.trim();
208
241
  const satisfied = range === '' ? false : satisfiesRange(facts.runtimeVersion, range, { includePrerelease: true });
209
- if (satisfied === false) {
210
- // Which side of the range the runtime sits on decides the remedy:
211
- // below the floor, upgrading the host genuinely helps; above the cap
212
- // (#757's own shape) no upgrade can satisfy it, and suggesting one
213
- // points the wrong way (#758).
214
- const floor = /[0-9]+\.[0-9]+\.[0-9]+[-+0-9A-Za-z.]*/.exec(range);
215
- let hostTooOld = false;
216
- if (floor !== null)
217
- hostTooOld = compareSemver(facts.runtimeVersion, floor[0]) < 0;
218
- return { peer, range, runtimeVersion: facts.runtimeVersion, hostTooOld };
219
- }
242
+ if (satisfied === false)
243
+ return unmetOf(peer, range, facts.runtimeVersion);
220
244
  }
221
245
  return null;
222
246
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "dshmarket",
3
3
  "description": "Visual plugin market inside DeepSeek Harness — browse, search, and one-click install community plugins. · DSH 可视化插件市场:逛一逛,点一下,装好。",
4
- "version": "1.66.25",
4
+ "version": "1.66.26",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
7
7
  "types": "lib/types/index.d.ts",
package/src/check.ts CHANGED
@@ -520,6 +520,31 @@ function parseSemver(value: string): Semver | null {
520
520
  }
521
521
  }
522
522
 
523
+ /**
524
+ * Parse a comparator's target the way node-semver does: a PARTIAL version in
525
+ * a range is zero-filled, so `<0.3` is `<0.3.0` and `>=2` is `>=2.0.0`.
526
+ *
527
+ * Without this, every prerelease floor written as a partial — the
528
+ * `@deepseek-ai/dsh-*` peers of a whole family of connector plugins say
529
+ * `>=0.1.5 <0.3`, `>=0.1.7-rc <2`, `>=0.2.0-rc.0 <0.3` — parsed as unknown,
530
+ * `satisfiesRange` answered null, and the gate stood aside: measured against
531
+ * the host's own evaluator over 699 live catalog manifests, those were the
532
+ * last 7 of 2796 (package, runtime) pairs where the host refuses a plugin
533
+ * and the market still promised a restart would apply it.
534
+ *
535
+ * A BARE partial (`1.2`) is a different thing in semver — an x-range that
536
+ * means `>=1.2.0 <1.3.0` — so it deliberately stays unknown here rather than
537
+ * being mistaken for the exact version `1.2.0`.
538
+ */
539
+ function parseComparatorTarget(target: string, op: string): Semver | null {
540
+ const direct = parseSemver(target)
541
+ if (direct !== null) return direct
542
+ if (op === '') return null
543
+ const parts = target.trim().split('.')
544
+ if (parts.length > 3 || parts.some(part => !/^\d+$/u.test(part))) return null
545
+ return parseSemver([...parts, ...Array<string>(3 - parts.length).fill('0')].join('.'))
546
+ }
547
+
523
548
  function comparePre(a: string[], b: string[]): number {
524
549
  const len = Math.max(a.length, b.length)
525
550
  for (let i = 0; i < len; i += 1) {
@@ -643,7 +668,7 @@ export function satisfiesRange(
643
668
  const m = /^(\^|~|>=|<=|>|<)?(.*)$/.exec(p)
644
669
  const op = m?.[1] ?? ''
645
670
  const target = (m?.[2] ?? '').trim()
646
- const tv = parseSemver(target)
671
+ const tv = parseComparatorTarget(target, op)
647
672
  if (tv === null) return null
648
673
  const major = tv.major
649
674
  const minor = tv.minor
@@ -688,7 +713,7 @@ export function satisfiesRange(
688
713
  // prerelease admission gate. Otherwise an unknown protocol such as
689
714
  // `workspace:^` or `catalog:default` is misreported as a definite false
690
715
  // whenever the resolved version happens to carry a prerelease tag.
691
- if (parseSemver(target) === null) return null
716
+ if (parseComparatorTarget(target, m[1] ?? '') === null) return null
692
717
  return { op: m[1] ?? '', target }
693
718
  }
694
719
 
package/src/verify.ts CHANGED
@@ -251,10 +251,28 @@ export function hostPeerGate(activeProfileDir: string, name: string, facts: Host
251
251
  return peerGateAt(join(activeProfileDir, 'node_modules', name), name, facts)
252
252
  }
253
253
 
254
+ /**
255
+ * The unmet requirement, if any, and the remedy side it points at.
256
+ *
257
+ * Which side of the range the runtime sits on decides the remedy: below the
258
+ * floor, upgrading the host genuinely helps; above the cap (#757's own
259
+ * shape) no upgrade can satisfy it, and suggesting one points the wrong way
260
+ * (#758).
261
+ */
262
+ function unmetOf(peer: string, range: string, runtimeVersion: string): PeerGate {
263
+ const floor = /[0-9]+\.[0-9]+\.[0-9]+[-+0-9A-Za-z.]*/.exec(range)
264
+ const hostTooOld = floor !== null && compareSemver(runtimeVersion, floor[0]) < 0
265
+ return { peer, range, runtimeVersion, hostTooOld }
266
+ }
267
+
254
268
  /** Same prediction for a patch plugin resolved outside the profile's flat node_modules. */
255
269
  function peerGateAt(packageDir: string, name: string, facts: HostRuntimeFacts): PeerGate | null {
256
270
  if (facts.runtimeVersion === null) return null
257
- let manifest: { version?: unknown; peerDependencies?: Record<string, unknown> }
271
+ let manifest: {
272
+ version?: unknown
273
+ engines?: Record<string, unknown>
274
+ peerDependencies?: Record<string, unknown>
275
+ }
258
276
  try {
259
277
  manifest = JSON.parse(
260
278
  readFileSync(join(packageDir, 'package.json'), 'utf8'),
@@ -266,20 +284,30 @@ function peerGateAt(packageDir: string, name: string, facts: HostRuntimeFacts):
266
284
  if (version !== '' && (facts.exemptions[`${name}@${version}`] ?? []).includes(facts.runtimeVersion)) {
267
285
  return null
268
286
  }
287
+ // An own `engines.dsh` range decides ALONE and peerDependencies are then
288
+ // ignored — the host's rule, read from unmetRequirements in
289
+ // @deepseek-ai/dsh-app-boot, and the reason a gate that only read peers
290
+ // was too lenient on 62 of 2796 (package, runtime) pairs measured over the
291
+ // live catalog: `dsh-file-explorer-kit` (engines.dsh ^0.1.1) and
292
+ // `dsh-workspace-kit` (^0.1.2-rc.1) declare NO dsh peer at all, so the
293
+ // family that refuses them was invisible here and the card promised a
294
+ // restart the host would not honour. A non-string range throws inside the
295
+ // host's own evaluator; that is not a verdict this can predict, so the
296
+ // old answer stands (#757).
297
+ const engines = manifest.engines?.dsh
298
+ if (engines !== undefined) {
299
+ if (typeof engines !== 'string') return null
300
+ const range = engines.trim()
301
+ // `workspace:` is substituted only for PEERS: the host counts it as an
302
+ // unmet range for engines, so an empty or invalid range stays unmet too.
303
+ const satisfied = range === '' ? false : satisfiesRange(facts.runtimeVersion, range, { includePrerelease: true })
304
+ return satisfied ? null : unmetOf('engines.dsh', range, facts.runtimeVersion)
305
+ }
269
306
  for (const [peer, requirement] of Object.entries(manifest.peerDependencies ?? {})) {
270
307
  if (!isDshPeer(peer) || typeof requirement !== 'string') continue
271
308
  const range = requirement.trim()
272
309
  const satisfied = range === '' ? false : satisfiesRange(facts.runtimeVersion, range, { includePrerelease: true })
273
- if (satisfied === false) {
274
- // Which side of the range the runtime sits on decides the remedy:
275
- // below the floor, upgrading the host genuinely helps; above the cap
276
- // (#757's own shape) no upgrade can satisfy it, and suggesting one
277
- // points the wrong way (#758).
278
- const floor = /[0-9]+\.[0-9]+\.[0-9]+[-+0-9A-Za-z.]*/.exec(range)
279
- let hostTooOld = false
280
- if (floor !== null) hostTooOld = compareSemver(facts.runtimeVersion, floor[0]) < 0
281
- return { peer, range, runtimeVersion: facts.runtimeVersion, hostTooOld }
282
- }
310
+ if (satisfied === false) return unmetOf(peer, range, facts.runtimeVersion)
283
311
  }
284
312
  return null
285
313
  }