dsh-plugin-rack 0.1.1 → 0.1.3

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/index.js CHANGED
@@ -63,10 +63,14 @@ import { request as httpsRequest } from 'node:https'
63
63
  import { connect as netConnect } from 'node:net'
64
64
  import { connect as tlsConnect } from 'node:tls'
65
65
  import { createRequire } from 'node:module'
66
+ import { EXACT_VERSION, compareVersions, isVersionSpec, parseSpec, satisfiesSpec } from './lib/spec.js'
66
67
 
67
68
  const ROUTE_PREFIX = '/api/dsh-plugin-rack'
68
69
  const require = createRequire(import.meta.url)
69
- const VERSION = '0.1.0'
70
+ // Read from package.json instead of a literal: the panel reports this number to
71
+ // the user and to the update checker, and a hand-maintained copy drifted from
72
+ // the published version the first time it mattered.
73
+ const VERSION = require('./package.json').version
70
74
  // The package was called dsh-plugin-dashboard before the public release. Keep
71
75
  // the old name recognised so a half-migrated install still treats the panel as
72
76
  // itself — otherwise "turn everything off" would switch off the very route
@@ -1501,8 +1505,6 @@ async function readJsonBody(req) {
1501
1505
 
1502
1506
  /** Packages the runtime itself provides; those are never fetched from npm. */
1503
1507
  const HOST_SCOPE = /^@deepseek-ai\//
1504
- /** A version pinned exactly — the profile's own convention for dsh bundles. */
1505
- const EXACT_VERSION = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/
1506
1508
  const REGISTRY_TIMEOUT = 12000
1507
1509
  const UPDATE_CONCURRENCY = 6
1508
1510
 
@@ -1522,44 +1524,6 @@ function profileFacts(ctx) {
1522
1524
  }
1523
1525
  }
1524
1526
 
1525
- function versionParts(value) {
1526
- const match = /^v?(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?(?:\+.*)?$/.exec(String(value).trim())
1527
- if (match === null) return null
1528
- return {
1529
- numbers: [Number(match[1]), Number(match[2]), Number(match[3])],
1530
- pre: match[4] === undefined ? null : match[4].split('.'),
1531
- }
1532
- }
1533
-
1534
- /** Standard semver ordering; an unparsable side compares as equal. */
1535
- function compareVersions(left, right) {
1536
- const a = versionParts(left)
1537
- const b = versionParts(right)
1538
- if (a === null || b === null) return 0
1539
- for (let index = 0; index < 3; index += 1) {
1540
- if (a.numbers[index] !== b.numbers[index]) return a.numbers[index] < b.numbers[index] ? -1 : 1
1541
- }
1542
- if (a.pre === null && b.pre === null) return 0
1543
- if (a.pre === null) return 1
1544
- if (b.pre === null) return -1
1545
- const length = Math.max(a.pre.length, b.pre.length)
1546
- for (let index = 0; index < length; index += 1) {
1547
- const x = a.pre[index]
1548
- const y = b.pre[index]
1549
- if (x === undefined) return -1
1550
- if (y === undefined) return 1
1551
- const xNumeric = /^\d+$/.test(x)
1552
- const yNumeric = /^\d+$/.test(y)
1553
- if (xNumeric && yNumeric) {
1554
- if (Number(x) !== Number(y)) return Number(x) < Number(y) ? -1 : 1
1555
- continue
1556
- }
1557
- if (xNumeric !== yNumeric) return xNumeric ? -1 : 1
1558
- if (x !== y) return x < y ? -1 : 1
1559
- }
1560
- return 0
1561
- }
1562
-
1563
1527
  /**
1564
1528
  * The registry answer for a package's `latest` dist-tag. The profile's own
1565
1529
  * registry is asked first — a user behind a mirror should not be forced through
@@ -1597,15 +1561,29 @@ async function npmLatest(name, base, signal) {
1597
1561
  throw new Error(errors.join('; '))
1598
1562
  }
1599
1563
 
1600
- /** Dependencies this panel is willing to touch: not host-provided, pinned exactly. */
1564
+ /**
1565
+ * Dependencies this panel is willing to touch: host-provided ones are skipped,
1566
+ * and every spec it understands becomes a target.
1567
+ *
1568
+ * This used to accept exact pins only, which quietly meant that a profile using
1569
+ * `^1.2.3` — the normal way to write a dependency — had those packages invisible
1570
+ * to "update everything". The range still matters though: it is kept on the
1571
+ * target so the checker can refuse a registry version the user's own range
1572
+ * excludes, instead of silently jumping a major.
1573
+ */
1601
1574
  function eligibleDeps(facts) {
1602
1575
  const out = []
1603
1576
  for (const [name, spec] of Object.entries(facts.deps)) {
1604
1577
  if (HOST_SCOPE.test(name)) continue
1605
1578
  if (typeof spec !== 'string') continue
1606
- const pinned = spec.trim()
1607
- if (!EXACT_VERSION.test(pinned)) continue
1608
- out.push({ name, current: pinned })
1579
+ const parsed = parseSpec(spec)
1580
+ if (parsed.kind === 'unsupported') continue
1581
+ // What is actually installed beats the range floor: with `^1.2.3` installed
1582
+ // at 1.9.0, comparing against the floor would report an update to 1.9.0.
1583
+ const installed = installedVersion(facts.dir, name)
1584
+ const current = installed !== null ? installed : parsed.floor
1585
+ if (current === null) continue
1586
+ out.push({ name, current, spec: spec.trim(), kind: parsed.kind, floor: parsed.floor })
1609
1587
  }
1610
1588
  return out
1611
1589
  }
@@ -1625,11 +1603,18 @@ async function checkUpdates(ctx) {
1625
1603
  const target = targets[index]
1626
1604
  try {
1627
1605
  const latest = await npmLatest(target.name, base, activeJobAbort === null ? undefined : activeJobAbort.signal)
1606
+ const ahead = compareVersions(latest, target.current) > 0
1607
+ // An exact pin has no range to violate. Anything else does, and a
1608
+ // major bump the user's own `^`/`~` excludes is not this panel's call.
1609
+ const inRange = target.kind === 'exact' || satisfiesSpec(latest, target.spec)
1628
1610
  rows[index] = {
1629
1611
  name: target.name,
1630
1612
  current: target.current,
1631
1613
  latest,
1632
- hasUpdate: compareVersions(latest, target.current) > 0,
1614
+ spec: target.spec,
1615
+ kind: target.kind,
1616
+ hasUpdate: ahead && inRange,
1617
+ outOfRange: ahead && !inRange,
1633
1618
  error: null,
1634
1619
  }
1635
1620
  } catch (error) {
@@ -1637,7 +1622,10 @@ async function checkUpdates(ctx) {
1637
1622
  name: target.name,
1638
1623
  current: target.current,
1639
1624
  latest: null,
1625
+ spec: target.spec,
1626
+ kind: target.kind,
1640
1627
  hasUpdate: false,
1628
+ outOfRange: false,
1641
1629
  error: error instanceof Error ? error.message : String(error),
1642
1630
  }
1643
1631
  }
@@ -2131,6 +2119,7 @@ async function applyUpdates(ctx, names, pinned = null, job = null) {
2131
2119
  if (targets.length === 0) return { ok: false, message: 'no eligible package to update' }
2132
2120
 
2133
2121
  const planned = []
2122
+ const outOfRange = []
2134
2123
  const registryBase = await configuredRegistry(ctx)
2135
2124
  for (const target of targets) {
2136
2125
  // 每查一个都先看有没有被取消:registry 查询 + 代理重试是弱网下最长的一段,
@@ -2145,10 +2134,29 @@ async function applyUpdates(ctx, names, pinned = null, job = null) {
2145
2134
  }
2146
2135
  }
2147
2136
  if (compareVersions(latest, target.current) <= 0) continue
2137
+ // The user's own range is a decision, not an obstacle: `^1.2.3` says 2.x is
2138
+ // out of bounds, so installing it because the registry moved on would be
2139
+ // this panel overriding them. Report it instead of applying it silently.
2140
+ if (target.kind !== 'exact' && !satisfiesSpec(latest, target.spec)) {
2141
+ outOfRange.push({ name: target.name, spec: target.spec, latest })
2142
+ continue
2143
+ }
2148
2144
  planned.push({ name: target.name, current: target.current, latest })
2149
2145
  }
2150
2146
  assertNotCancelled(job)
2151
- if (planned.length === 0) return { ok: true, updated: [], message: 'already current' }
2147
+ if (planned.length === 0) {
2148
+ if (outOfRange.length > 0) {
2149
+ return {
2150
+ ok: true,
2151
+ updated: [],
2152
+ outOfRange,
2153
+ message: outOfRange
2154
+ .map(one => `${one.name} ${one.latest} 超出了 package.json 写明的 ${one.spec},没有安装`)
2155
+ .join(';'),
2156
+ }
2157
+ }
2158
+ return { ok: true, updated: [], message: 'already current' }
2159
+ }
2152
2160
 
2153
2161
  // Everything pnpm may touch, kept for the rollback below.
2154
2162
  const backup = new Map()
@@ -2319,6 +2327,9 @@ async function applyUpdates(ctx, names, pinned = null, job = null) {
2319
2327
  return {
2320
2328
  ok: true,
2321
2329
  updated: planned.map(item => ({ name: item.name, from: item.current, to: item.latest })),
2330
+ // Reported rather than swallowed: the user asked to update these, and the
2331
+ // honest answer is "the range you wrote excludes the new version".
2332
+ ...(outOfRange.length > 0 ? { outOfRange } : {}),
2322
2333
  hotSwap: swap,
2323
2334
  restart: swap.ok !== true,
2324
2335
  // 没有维护门时如实上报:用户有权知道这次是在会话可能正跑着的时候改了 node_modules。
@@ -2893,9 +2904,6 @@ async function pipeTo(part, response, signal, offset, deadline) {
2893
2904
  stream.on('error', error => {
2894
2905
  if (failure === null) failure = error
2895
2906
  })
2896
- const fail = error => {
2897
- if (failure === null) failure = error
2898
- }
2899
2907
 
2900
2908
  let idle = null
2901
2909
  const arm = () => {
@@ -3112,7 +3120,9 @@ async function listExtras(ctx) {
3112
3120
  for (const [name, raw] of Object.entries(facts.deps)) {
3113
3121
  if (HOST_SCOPE.test(name)) continue
3114
3122
  const spec = typeof raw === 'string' ? raw.trim() : ''
3115
- if (EXACT_VERSION.test(spec)) continue
3123
+ // Everything the version updater understands is not an "other install kind":
3124
+ // ranges used to land here as `other` rows that offered no action at all.
3125
+ if (isVersionSpec(spec)) continue
3116
3126
  const current = installedVersion(facts.dir, name)
3117
3127
  if (spec.startsWith('link:')) {
3118
3128
  rows.push({ name, kind: 'link', spec, current, note: '本地软链' })
package/lib/spec.js ADDED
@@ -0,0 +1,285 @@
1
+ /**
2
+ * Version specs, parsed and compared — the whole of what this plugin needs to
3
+ * understand about semver.
4
+ *
5
+ * These functions are pure and have no imports, so they can be unit-tested
6
+ * directly and type-checked without pulling the host into scope. Everything
7
+ * that decides "is there an update" goes through here, which is why they are
8
+ * the only part of the plugin covered by `tsc --checkJs`.
9
+ *
10
+ * The supported shapes are the ones a `package.json` in the wild actually uses:
11
+ * an exact pin, `^`, `~`, `>=`, `>`, `<`, `<=`, a bare or wildcard prefix
12
+ * (`1`, `1.x`, `1.2`, `1.2.x`), a hyphen range (`1.2.3 - 2.0.0`), and unions of
13
+ * those with `||`. Anything else — `link:`, `file:`, `github:`, a tarball URL,
14
+ * an npm alias — is `unsupported`, which is the signal to route it through the
15
+ * "其他安装方式" path instead of the version updater.
16
+ */
17
+
18
+ /** A registry version: 1.2.3, optionally with a prerelease suffix. */
19
+ export const EXACT_VERSION = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/
20
+
21
+ /**
22
+ * @typedef {'exact' | 'caret' | 'tilde' | 'xrange' | 'hyphen' | 'gt' | 'gte' | 'lt' | 'lte' | 'any' | 'union' | 'unsupported'} SpecKind
23
+ *
24
+ * @typedef {object} ParsedSpec
25
+ * @property {SpecKind} kind how the range is written
26
+ * @property {string | null} floor the lowest version the spec allows, zeros filled in
27
+ * @property {number} parts how many components the user actually wrote (1, 2 or 3)
28
+ * @property {string | null} [ceiling] inclusive or exclusive upper version (hyphen, lt, lte)
29
+ * @property {ParsedSpec[]} [ranges] the alternatives of a union
30
+ *
31
+ * @typedef {object} VersionParts
32
+ * @property {number[]} numbers always three entries
33
+ * @property {string[] | null} pre prerelease identifiers, or null for a release
34
+ */
35
+
36
+ /**
37
+ * Split a version into its numeric core and prerelease identifiers.
38
+ *
39
+ * @param {string} value
40
+ * @returns {VersionParts | null}
41
+ */
42
+ export function versionParts(value) {
43
+ if (typeof value !== 'string') return null
44
+ const trimmed = value.trim().replace(/^v/, '')
45
+ const match = /^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?$/.exec(trimmed)
46
+ if (match === null) return null
47
+ const pre = match[4] === undefined ? null : match[4].split('.')
48
+ return { numbers: [Number(match[1]), Number(match[2]), Number(match[3])], pre }
49
+ }
50
+
51
+ /**
52
+ * Semver ordering, prerelease rules included: 1.0.0-rc.1 < 1.0.0, and a
53
+ * numeric prerelease identifier sorts below an alphanumeric one.
54
+ *
55
+ * @param {string} left
56
+ * @param {string} right
57
+ * @returns {number} -1, 0 or 1
58
+ */
59
+ export function compareVersions(left, right) {
60
+ const a = versionParts(left)
61
+ const b = versionParts(right)
62
+ if (a === null || b === null) return 0
63
+ for (let index = 0; index < 3; index += 1) {
64
+ if (a.numbers[index] !== b.numbers[index]) return a.numbers[index] < b.numbers[index] ? -1 : 1
65
+ }
66
+ if (a.pre === null && b.pre === null) return 0
67
+ if (a.pre === null) return 1
68
+ if (b.pre === null) return -1
69
+ const length = Math.max(a.pre.length, b.pre.length)
70
+ for (let index = 0; index < length; index += 1) {
71
+ const x = a.pre[index]
72
+ const y = b.pre[index]
73
+ if (x === undefined) return -1
74
+ if (y === undefined) return 1
75
+ const xNumeric = /^\d+$/.test(x)
76
+ const yNumeric = /^\d+$/.test(y)
77
+ if (xNumeric && yNumeric) {
78
+ if (Number(x) !== Number(y)) return Number(x) < Number(y) ? -1 : 1
79
+ continue
80
+ }
81
+ if (xNumeric !== yNumeric) return xNumeric ? -1 : 1
82
+ if (x !== y) return x < y ? -1 : 1
83
+ }
84
+ return 0
85
+ }
86
+
87
+ /**
88
+ * Expand a partially written version to three components.
89
+ *
90
+ * `1` counts as one component, `1.2` as two — the count is what tells a caret
91
+ * or tilde range how wide it may go.
92
+ *
93
+ * @param {string} value `1`, `1.2` or `1.2.3`, with an optional prerelease
94
+ * @returns {{floor: string | null, parts: number}}
95
+ */
96
+ function expand(value) {
97
+ const trimmed = value.trim().replace(/^v/, '')
98
+ const core = /^(\d+)(?:\.(\d+))?(?:\.(\d+))?(-[0-9A-Za-z.-]+)?$/.exec(trimmed)
99
+ if (core === null) return { floor: null, parts: 0 }
100
+ const parts = core[3] !== undefined ? 3 : core[2] !== undefined ? 2 : 1
101
+ const floor = `${Number(core[1])}.${core[2] === undefined ? 0 : Number(core[2])}.${core[3] === undefined ? 0 : Number(core[3])}${core[4] === undefined ? '' : core[4]}`
102
+ return { floor, parts }
103
+ }
104
+
105
+ const UNSUPPORTED = /** @type {const} */ ({ kind: 'unsupported', floor: null, parts: 0 })
106
+
107
+ /**
108
+ * Classify a dependency spec.
109
+ *
110
+ * @param {string} spec the raw value from package.json
111
+ * @returns {ParsedSpec}
112
+ */
113
+ export function parseSpec(spec) {
114
+ const raw = typeof spec === 'string' ? spec.trim() : ''
115
+ if (raw === '') return { kind: 'any', floor: null, parts: 0 }
116
+
117
+ // A union is only a union at the top level: `^1 || ^2` is two full specs.
118
+ if (raw.includes('||')) {
119
+ const ranges = raw
120
+ .split('||')
121
+ .map((part) => parseSpec(part))
122
+ .filter((part) => part.kind !== 'unsupported' && part.kind !== 'any')
123
+ if (ranges.length === 0) return { kind: 'any', floor: null, parts: 0 }
124
+ if (ranges.length === 1) return ranges[0]
125
+ return { kind: 'union', floor: null, parts: 0, ranges }
126
+ }
127
+
128
+ // `1.2.3 - 2.0.0`: hyphen ranges need the spaces, otherwise the dash is a
129
+ // prerelease separator (`1.2.3-rc.1`).
130
+ const hyphen = /^(\d[\w.-]*)\s+-\s+(\d[\w.-]*)$/.exec(raw)
131
+ if (hyphen !== null) {
132
+ const from = expand(hyphen[1])
133
+ const to = expand(hyphen[2])
134
+ if (from.floor === null || to.floor === null) return UNSUPPORTED
135
+ return { kind: 'hyphen', floor: from.floor, parts: from.parts, ceiling: to.floor }
136
+ }
137
+
138
+ if (raw === '*' || raw === 'x' || raw === 'X' || raw === 'latest') {
139
+ return { kind: 'any', floor: null, parts: 0 }
140
+ }
141
+ if (EXACT_VERSION.test(raw)) return { kind: 'exact', floor: raw, parts: 3 }
142
+
143
+ const operators = [
144
+ ['^', 'caret'],
145
+ ['~', 'tilde'],
146
+ ['>=', 'gte'],
147
+ ['>', 'gt'],
148
+ ['<=', 'lte'],
149
+ ['<', 'lt'],
150
+ ]
151
+ for (const [prefix, kind] of operators) {
152
+ if (!raw.startsWith(prefix)) continue
153
+ const expanded = expand(raw.slice(prefix.length))
154
+ if (expanded.floor === null) return UNSUPPORTED
155
+ if (kind === 'lt' || kind === 'lte') {
156
+ // `<=1.2.3` and `<1.2.3` have no floor; the version itself is the bound.
157
+ return { kind: /** @type {SpecKind} */ (kind), floor: null, parts: expanded.parts, ceiling: expanded.floor }
158
+ }
159
+ return { kind: /** @type {SpecKind} */ (kind), floor: expanded.floor, parts: expanded.parts }
160
+ }
161
+
162
+ // A bare or wildcard prefix: `1`, `1.x`, `1.2`, `1.2.x`, `1.2.*`.
163
+ const wildcard = /^(\d+)(?:\.(\d+|[xX*]))?(?:\.(\d+|[xX*]))?$/.exec(raw)
164
+ if (wildcard !== null) {
165
+ const major = Number(wildcard[1])
166
+ const minorToken = wildcard[2]
167
+ const patchToken = wildcard[3]
168
+ const minor = minorToken === undefined || !/^\d+$/.test(minorToken) ? null : Number(minorToken)
169
+ const patch = patchToken === undefined || !/^\d+$/.test(patchToken) ? null : Number(patchToken)
170
+ const parts = patch !== null ? 3 : minor !== null ? 2 : 1
171
+ const floor = `${major}.${minor === null ? 0 : minor}.${patch === null ? 0 : patch}`
172
+ return { kind: 'xrange', floor, parts }
173
+ }
174
+
175
+ return UNSUPPORTED
176
+ }
177
+
178
+ /**
179
+ * The first version outside a caret, tilde or wildcard range.
180
+ *
181
+ * @param {ParsedSpec} parsed
182
+ * @returns {number[] | null}
183
+ */
184
+ function ceilingOf(parsed) {
185
+ if (parsed.floor === null) return null
186
+ const parts = versionParts(parsed.floor)
187
+ if (parts === null) return null
188
+ const [major, minor, patch] = parts.numbers
189
+ if (parsed.kind === 'caret') {
190
+ // ^0.2.3 means "0.2.x and up", not "0.x" — the zero-major rule.
191
+ if (major > 0) return [major + 1, 0, 0]
192
+ if (minor > 0) return [0, minor + 1, 0]
193
+ return [0, 0, patch + 1]
194
+ }
195
+ if (parsed.kind === 'tilde' || parsed.kind === 'xrange') {
196
+ // ~1.2.3 and ~1.2 both stay inside 1.2; only a bare ~1 (or `1.x`) widens
197
+ // to 2.0.0. `1.2` behaves like `~1.2`, which is what npm does.
198
+ if (parsed.parts === 1) return [major + 1, 0, 0]
199
+ if (parsed.parts === 2) return [major, minor + 1, 0]
200
+ return [major, minor + 1, 0]
201
+ }
202
+ return null
203
+ }
204
+
205
+ /**
206
+ * Is `version` below `ceiling` (exclusive)?
207
+ *
208
+ * @param {VersionParts} target
209
+ * @param {number[]} ceiling
210
+ * @returns {boolean}
211
+ */
212
+ function below(target, ceiling) {
213
+ for (let index = 0; index < 3; index += 1) {
214
+ if (target.numbers[index] !== ceiling[index]) return target.numbers[index] < ceiling[index]
215
+ }
216
+ return false
217
+ }
218
+
219
+ /**
220
+ * The shared decision, on an already-parsed spec.
221
+ *
222
+ * @param {string} version
223
+ * @param {ParsedSpec} parsed
224
+ * @returns {boolean}
225
+ */
226
+ function satisfiesParsed(version, parsed) {
227
+ if (parsed.kind === 'any') return true
228
+ if (parsed.kind === 'unsupported') return false
229
+ if (parsed.kind === 'union') {
230
+ return (parsed.ranges ?? []).some((one) => satisfiesParsed(version, one))
231
+ }
232
+ if (parsed.kind === 'exact') return parsed.floor !== null && compareVersions(version, parsed.floor) === 0
233
+
234
+ const target = versionParts(version)
235
+ if (target === null) return false
236
+
237
+ // A prerelease only passes when the spec itself asked for one. Without this,
238
+ // `^1.2.3` would quietly accept `2.0.0-rc.1` just because it sorts above the
239
+ // floor of a different range.
240
+ const floorParts = parsed.floor === null ? null : versionParts(parsed.floor)
241
+ const ceilingParts = parsed.ceiling === undefined || parsed.ceiling === null ? null : versionParts(parsed.ceiling)
242
+ const specHasPrerelease = (floorParts !== null && floorParts.pre !== null) || (ceilingParts !== null && ceilingParts.pre !== null)
243
+ if (target.pre !== null && !specHasPrerelease) return false
244
+
245
+ if (parsed.kind === 'lt' || parsed.kind === 'lte') {
246
+ if (ceilingParts === null) return false
247
+ const order = compareVersions(version, parsed.ceiling ?? '')
248
+ return parsed.kind === 'lte' ? order <= 0 : order < 0
249
+ }
250
+
251
+ if (parsed.kind === 'hyphen') {
252
+ if (floorParts === null || ceilingParts === null) return false
253
+ return compareVersions(version, parsed.floor ?? '') >= 0 && compareVersions(version, parsed.ceiling ?? '') <= 0
254
+ }
255
+
256
+ if (floorParts === null) return false
257
+ const order = compareVersions(version, parsed.floor ?? '')
258
+ if (parsed.kind === 'gte') return order >= 0
259
+ if (parsed.kind === 'gt') return order > 0
260
+ if (order < 0) return false
261
+ const ceiling = ceilingOf(parsed)
262
+ if (ceiling === null) return false
263
+ return below(target, ceiling)
264
+ }
265
+
266
+ /**
267
+ * Is `version` allowed by `spec`?
268
+ *
269
+ * @param {string} version
270
+ * @param {string} spec the raw spec, parsed here
271
+ * @returns {boolean}
272
+ */
273
+ export function satisfiesSpec(version, spec) {
274
+ return satisfiesParsed(version, parseSpec(spec))
275
+ }
276
+
277
+ /**
278
+ * A spec this plugin's version updater may rewrite.
279
+ *
280
+ * @param {string} spec
281
+ * @returns {boolean}
282
+ */
283
+ export function isVersionSpec(spec) {
284
+ return parseSpec(spec).kind !== 'unsupported'
285
+ }
package/package.json CHANGED
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "name": "dsh-plugin-rack",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "type": "module",
5
5
  "description": "DSH 插件管理:所有插件一屏看完,一个开关直接开关,更新完自动热替换不用重启。/ Plugin rack for DSH: every plugin in one list with a power switch, an auto/manual flag that scopes the one-click bulk actions, and hot-swapped updates that need no restart.",
6
6
  "author": "Jaside (LZM-P)",
7
7
  "license": "MIT",
8
8
  "repository": {
9
9
  "type": "git",
10
- "url": "https://github.com/LZM-P/dsh-plugin-rack"
10
+ "url": "git+https://github.com/LZM-P/dsh-plugin-rack.git"
11
11
  },
12
12
  "homepage": "https://github.com/LZM-P/dsh-plugin-rack#readme",
13
13
  "bugs": "https://github.com/LZM-P/dsh-plugin-rack/issues",
@@ -35,16 +35,20 @@
35
35
  "files": [
36
36
  "index.js",
37
37
  "client.js",
38
+ "lib/*.js",
38
39
  "cordis.patch.yml",
39
40
  "locale/*.json",
40
41
  "icon.svg",
41
42
  "LICENSE",
42
- "README.md",
43
- "test/*.mjs"
43
+ "README.md"
44
44
  ],
45
45
  "scripts": {
46
- "check": "node test/self-check.mjs && node test/behaviour.mjs",
47
- "test": "node test/self-check.mjs && node test/behaviour.mjs && node test/job-background.mjs && node test/download.mjs && node test/pnpm-command.mjs"
46
+ "check": "node scripts/build-client.mjs --check && node test/self-check.mjs && node test/behaviour.mjs && node test/spec.mjs",
47
+ "test": "node scripts/build-client.mjs --check && node test/self-check.mjs && node test/behaviour.mjs && node test/spec.mjs && node test/client.mjs && node test/job-background.mjs && node test/download.mjs && node test/pnpm-command.mjs",
48
+ "lint": "eslint .",
49
+ "types": "tsc -p tsconfig.json",
50
+ "publish": "node scripts/publish.mjs",
51
+ "build": "node scripts/build-client.mjs"
48
52
  },
49
53
  "dsh": {
50
54
  "manifestVersion": 1,
@@ -61,15 +65,20 @@
61
65
  ]
62
66
  },
63
67
  "engines": {
64
- "dsh": ">=0.2.1-rc.2 <0.4.0-0"
68
+ "dsh": ">=0.2.1-rc.2 <0.2.3-0"
65
69
  },
66
70
  "compatibility": {
67
71
  "dshReleases": {
68
72
  "0.2.1-rc.2": "compatible",
69
73
  "0.2.1-rc.5": "compatible",
70
74
  "0.2.2-rc.1": "compatible",
71
- "0.2.2-rc.2": "compatible"
75
+ "0.2.2-rc.2": "compatible",
76
+ "0.2.2-rc.3": "compatible"
72
77
  }
73
78
  }
79
+ },
80
+ "devDependencies": {
81
+ "eslint": "^9.15.0",
82
+ "typescript": "^5.6.3"
74
83
  }
75
84
  }