dsh-dbhub-live 5.2.0-dev.3 → 5.2.0

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.
@@ -1,234 +1,234 @@
1
- // Unit tests for the Host's version metadata (lib/meta.mjs): semver parsing and
2
- // precedence, the "is an update newer?" question the update checker asks, the
3
- // dsh >= 0.2.1 compatibility gate, and the runtime probes (plugin version from
4
- // package.json, dsh version from an installed package layout).
5
- //
6
- // The discovery walk is driven against a temp layout with a fake argv entry, so
7
- // these tests never touch the real dsh installation; no DSH_HOME / store paths
8
- // are involved at all (this module reads package.json files only).
9
-
10
- import test from 'node:test'
11
- import assert from 'node:assert/strict'
12
- import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
13
- import { tmpdir } from 'node:os'
14
- import { dirname, join } from 'node:path'
15
-
16
- import {
17
- DSH_MODERN_BASE,
18
- DSH_PACKAGE_NAMES,
19
- PLUGIN_NAME,
20
- atLeastBase,
21
- compareSemver,
22
- detectDshRuntime,
23
- dshIsModern,
24
- isNewer,
25
- parseSemver,
26
- readPluginVersion,
27
- versionMeta,
28
- walkDshVersion,
29
- } from '../lib/meta.mjs'
30
-
31
- // ── semver primitives ─────────────────────────────────────────────────────
32
-
33
- test('parseSemver accepts v-prefix, prerelease and build metadata', () => {
34
- assert.deepEqual(parseSemver('1.2.3').prerelease, [])
35
- assert.equal(parseSemver('v5.2.0').major, 5)
36
- assert.equal(parseSemver('v5.2.0').minor, 2)
37
- assert.equal(parseSemver(' v5.2.0-dev.1 ').patch, 0)
38
- assert.deepEqual(parseSemver('5.2.0-dev.1').prerelease, ['dev', '1'])
39
- // Prerelease AND build metadata together: only the prerelease is compared.
40
- assert.deepEqual(parseSemver('1.0.0-rc.1+build').prerelease, ['rc', '1'])
41
- // Build metadata never participates in parsing beyond being accepted.
42
- assert.deepEqual(parseSemver('1.0.0+sha.abc').prerelease, [])
43
- })
44
-
45
- test('parseSemver rejects non-semver junk instead of coercing it', () => {
46
- for (const bad of ['', ' ', 'latest', '1.2', '192.0.2.10', 'x.y.z', '1..2', '1.2.3-', null, undefined, 42, 'dev.1']) {
47
- assert.equal(parseSemver(bad), undefined, 'should reject: ' + String(bad))
48
- }
49
- // Empty prerelease identifiers are invalid semver.
50
- assert.equal(parseSemver('1.2.3-alpha..beta'), undefined)
51
- })
52
-
53
- test('compareSemver follows semver precedence (incl. prerelease ordering)', () => {
54
- // The canonical chain from semver.org §11.
55
- const chain = [
56
- '1.0.0-alpha',
57
- '1.0.0-alpha.1',
58
- '1.0.0-alpha.beta',
59
- '1.0.0-beta',
60
- '1.0.0-beta.2',
61
- '1.0.0-beta.11',
62
- '1.0.0-rc.1',
63
- '1.0.0',
64
- ]
65
- for (let i = 0; i < chain.length - 1; i += 1) {
66
- assert.equal(compareSemver(chain[i], chain[i + 1]), -1, chain[i] + ' < ' + chain[i + 1])
67
- assert.equal(compareSemver(chain[i + 1], chain[i]), 1, chain[i + 1] + ' > ' + chain[i])
68
- assert.equal(isNewer(chain[i + 1], chain[i]), true)
69
- assert.equal(isNewer(chain[i], chain[i + 1]), false)
70
- }
71
- // Numeric identifiers compare numerically (not lexically): 2 < 11.
72
- assert.equal(compareSemver('1.0.0-beta.2', '1.0.0-beta.11'), -1)
73
- // Build metadata is ignored for precedence.
74
- assert.equal(compareSemver('1.0.0+build.9', '1.0.0+sha.1'), 0)
75
- assert.equal(compareSemver('1.0.0', 'v1.0.0'), 0)
76
- // Triplet ordering.
77
- assert.equal(compareSemver('0.2.1', '0.2.0'), 1)
78
- assert.equal(compareSemver('5.2.0', '5.10.0'), -1)
79
- })
80
-
81
- test('compareSemver/isNewer never throw on invalid input', () => {
82
- assert.equal(compareSemver('nope', '1.0.0'), 0)
83
- assert.equal(compareSemver('1.0.0', undefined), 0)
84
- assert.equal(isNewer('5.2.0', ''), false, 'unknown current version never claims an update')
85
- assert.equal(isNewer('', '5.1.0'), false)
86
- assert.equal(isNewer('garbage', '5.1.0'), false)
87
- assert.equal(isNewer(undefined, undefined), false)
88
- // compareSemver also takes already-parsed objects.
89
- assert.equal(compareSemver(parseSemver('1.0.0'), '1.0.0'), 0)
90
- })
91
-
92
- test('isNewer is prerelease-aware for this repo dev builds', () => {
93
- // A dev build is OLDER than the release with the same base version ...
94
- assert.equal(isNewer('5.2.0', '5.2.0-dev.1'), true)
95
- assert.equal(isNewer('5.2.0-dev.2', '5.2.0-dev.1'), true)
96
- assert.equal(isNewer('5.2.0-dev.1', '5.2.0'), false)
97
- // ... and 5.2.0-dev.1 is NEWER than the previous 5.1.x release line.
98
- assert.equal(isNewer('5.2.0-dev.1', '5.1.0'), true)
99
- // Equal versions are never an update.
100
- assert.equal(isNewer('5.2.0', '5.2.0'), false)
101
- assert.equal(isNewer('5.2.0-dev.1', '5.2.0-dev.1'), false)
102
- })
103
-
104
- // ── the dsh >= 0.2.1 gate ─────────────────────────────────────────────────
105
-
106
- test('atLeastBase compares the numeric triplet and ignores prerelease', () => {
107
- assert.equal(atLeastBase('0.2.1-rc.2', DSH_MODERN_BASE), true, 'a 0.2.1 prerelease counts as 0.2.1')
108
- assert.equal(atLeastBase('0.2.1', DSH_MODERN_BASE), true)
109
- assert.equal(atLeastBase('0.2.2', DSH_MODERN_BASE), true)
110
- assert.equal(atLeastBase('1.0.0', DSH_MODERN_BASE), true)
111
- assert.equal(atLeastBase('0.2.0-rc.2', DSH_MODERN_BASE), false)
112
- assert.equal(atLeastBase('0.1.9', DSH_MODERN_BASE), false)
113
- assert.equal(atLeastBase('', DSH_MODERN_BASE), false)
114
- assert.equal(atLeastBase('unknown', DSH_MODERN_BASE), false)
115
- })
116
-
117
- test('dshIsModern: resolved version wins; unresolvable is modern only on desktop', () => {
118
- assert.equal(dshIsModern({ version: '0.2.1-rc.2', desktop: false }), true)
119
- assert.equal(dshIsModern({ version: '0.2.1-rc.2', desktop: true }), true)
120
- assert.equal(dshIsModern({ version: '0.1.6-alpha.2', desktop: true }), false, 'an old resolved version is old even on desktop')
121
- assert.equal(dshIsModern({ version: '', desktop: true }), true, 'desktop with an unresolvable version stays modern')
122
- assert.equal(dshIsModern({ version: '', desktop: false }), false, 'unknown web version => legacy UI')
123
- assert.equal(dshIsModern({}), false)
124
- assert.equal(dshIsModern(undefined), false)
125
- })
126
-
127
- // ── runtime discovery ─────────────────────────────────────────────────────
128
-
129
- function writePkg(dir, name, version) {
130
- mkdirSync(dir, { recursive: true })
131
- writeFileSync(join(dir, 'package.json'), JSON.stringify({ name, version }, null, 2))
132
- }
133
-
134
- test('walkDshVersion finds the installed dsh package above a fake argv entry', () => {
135
- const root = mkdtempSync(join(tmpdir(), 'dsh-meta-walk-'))
136
- try {
137
- // <root>/app is the dsh package; the entry point is <root>/app/lib/cli.js.
138
- writePkg(join(root, 'app'), '@deepseek-ai/dsh', '0.2.1-rc.2')
139
- const entry = join(root, 'app', 'lib', 'cli.js')
140
- mkdirSync(dirname(entry), { recursive: true })
141
- writeFileSync(entry, '// fake entry\n')
142
- assert.equal(walkDshVersion(entry), '0.2.1-rc.2')
143
-
144
- // A directory entry works too (some loaders pass one).
145
- assert.equal(walkDshVersion(join(root, 'app', 'lib')), '0.2.1-rc.2')
146
-
147
- // A nearer, non-dsh package.json is skipped rather than accepted.
148
- writePkg(join(root, 'app', 'lib'), 'some-other-package', '9.9.9')
149
- assert.equal(walkDshVersion(entry), '0.2.1-rc.2')
150
-
151
- // An unresolvable path simply yields ''.
152
- assert.equal(walkDshVersion(join(root, 'nope', 'nested', 'cli.js')), '')
153
- assert.equal(walkDshVersion(''), '')
154
- assert.equal(walkDshVersion(undefined), '')
155
- } finally {
156
- rmSync(root, { recursive: true, force: true })
157
- }
158
- })
159
-
160
- test('walkDshVersion honours the maxDepth budget', () => {
161
- const root = mkdtempSync(join(tmpdir(), 'dsh-meta-depth-'))
162
- try {
163
- writePkg(root, '@deepseek-ai/dsh', '0.2.1')
164
- // 10 directories between the entry file and the package root: beyond the
165
- // default 8-level walk, so the version must NOT be found.
166
- let dir = root
167
- const parts = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j']
168
- for (const part of parts) dir = join(dir, part)
169
- mkdirSync(dir, { recursive: true })
170
- const entry = join(dir, 'cli.js')
171
- writeFileSync(entry, '// fake entry\n')
172
- assert.equal(walkDshVersion(entry), '')
173
- assert.equal(walkDshVersion(entry, { maxDepth: 12 }), '0.2.1')
174
- // A package.json whose version is not semver is ignored.
175
- const other = mkdtempSync(join(tmpdir(), 'dsh-meta-badver-'))
176
- try {
177
- writePkg(join(other, 'pkg'), '@deepseek-ai/dsh', 'not-a-version')
178
- assert.equal(walkDshVersion(join(other, 'pkg', 'bin.js')), '')
179
- } finally {
180
- rmSync(other, { recursive: true, force: true })
181
- }
182
- } finally {
183
- rmSync(root, { recursive: true, force: true })
184
- }
185
- })
186
-
187
- test('walkDshVersion recognises every documented host package name', () => {
188
- assert.deepEqual([...DSH_PACKAGE_NAMES], [
189
- '@deepseek-ai/dsh',
190
- '@deepseek-ai/dsh-desktop-runtime',
191
- '@deepseek-ai/dsh-desktop',
192
- ])
193
- for (const name of DSH_PACKAGE_NAMES) {
194
- const root = mkdtempSync(join(tmpdir(), 'dsh-meta-name-'))
195
- try {
196
- writePkg(root, name, '0.2.1')
197
- assert.equal(walkDshVersion(join(root, 'lib', 'bin.js')), '0.2.1', name)
198
- } finally {
199
- rmSync(root, { recursive: true, force: true })
200
- }
201
- }
202
- })
203
-
204
- test('readPluginVersion equals this package.json version and is cached', () => {
205
- const pkg = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8'))
206
- assert.equal(PLUGIN_NAME, 'dsh-dbhub-live')
207
- assert.equal(readPluginVersion(), pkg.version)
208
- assert.equal(readPluginVersion(), pkg.version, 'the value is stable across calls')
209
- // The package.json version must itself be semver, otherwise the update check
210
- // would be comparing against an unknown version forever.
211
- assert.ok(parseSemver(readPluginVersion()), 'package.json version must be semver')
212
- })
213
-
214
- test('detectDshRuntime reports a coherent runtime (never throws)', () => {
215
- const runtime = detectDshRuntime()
216
- assert.equal(typeof runtime.version, 'string')
217
- assert.equal(typeof runtime.desktop, 'boolean')
218
- // This test process runs under plain node (no Electron), so unless the shell
219
- // exports DSH_CLIENT_VERSION the runtime must be web.
220
- if (!process.versions.electron && !process.env.DSH_CLIENT_VERSION) {
221
- assert.equal(runtime.desktop, false)
222
- assert.equal(runtime.version, '')
223
- }
224
- if (runtime.version) assert.ok(parseSemver(runtime.version), 'a resolved version is always semver')
225
- })
226
-
227
- test('versionMeta publishes the four view fields', () => {
228
- const meta = versionMeta()
229
- assert.deepEqual(Object.keys(meta).sort(), ['dshModern', 'dshVersion', 'pluginVersion', 'runtime'])
230
- assert.equal(meta.pluginVersion, readPluginVersion())
231
- assert.ok(meta.runtime === 'web' || meta.runtime === 'desktop')
232
- assert.equal(meta.dshModern, dshIsModern({ version: meta.dshVersion, desktop: meta.runtime === 'desktop' }))
233
- assert.equal(typeof meta.dshModern, 'boolean')
234
- })
1
+ // Unit tests for the Host's version metadata (lib/meta.mjs): semver parsing and
2
+ // precedence, the "is an update newer?" question the update checker asks, the
3
+ // dsh >= 0.2.1 compatibility gate, and the runtime probes (plugin version from
4
+ // package.json, dsh version from an installed package layout).
5
+ //
6
+ // The discovery walk is driven against a temp layout with a fake argv entry, so
7
+ // these tests never touch the real dsh installation; no DSH_HOME / store paths
8
+ // are involved at all (this module reads package.json files only).
9
+
10
+ import test from 'node:test'
11
+ import assert from 'node:assert/strict'
12
+ import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
13
+ import { tmpdir } from 'node:os'
14
+ import { dirname, join } from 'node:path'
15
+
16
+ import {
17
+ DSH_MODERN_BASE,
18
+ DSH_PACKAGE_NAMES,
19
+ PLUGIN_NAME,
20
+ atLeastBase,
21
+ compareSemver,
22
+ detectDshRuntime,
23
+ dshIsModern,
24
+ isNewer,
25
+ parseSemver,
26
+ readPluginVersion,
27
+ versionMeta,
28
+ walkDshVersion,
29
+ } from '../lib/meta.mjs'
30
+
31
+ // ── semver primitives ─────────────────────────────────────────────────────
32
+
33
+ test('parseSemver accepts v-prefix, prerelease and build metadata', () => {
34
+ assert.deepEqual(parseSemver('1.2.3').prerelease, [])
35
+ assert.equal(parseSemver('v5.2.0').major, 5)
36
+ assert.equal(parseSemver('v5.2.0').minor, 2)
37
+ assert.equal(parseSemver(' v5.2.0-dev.1 ').patch, 0)
38
+ assert.deepEqual(parseSemver('5.2.0-dev.1').prerelease, ['dev', '1'])
39
+ // Prerelease AND build metadata together: only the prerelease is compared.
40
+ assert.deepEqual(parseSemver('1.0.0-rc.1+build').prerelease, ['rc', '1'])
41
+ // Build metadata never participates in parsing beyond being accepted.
42
+ assert.deepEqual(parseSemver('1.0.0+sha.abc').prerelease, [])
43
+ })
44
+
45
+ test('parseSemver rejects non-semver junk instead of coercing it', () => {
46
+ for (const bad of ['', ' ', 'latest', '1.2', '192.0.2.10', 'x.y.z', '1..2', '1.2.3-', null, undefined, 42, 'dev.1']) {
47
+ assert.equal(parseSemver(bad), undefined, 'should reject: ' + String(bad))
48
+ }
49
+ // Empty prerelease identifiers are invalid semver.
50
+ assert.equal(parseSemver('1.2.3-alpha..beta'), undefined)
51
+ })
52
+
53
+ test('compareSemver follows semver precedence (incl. prerelease ordering)', () => {
54
+ // The canonical chain from semver.org §11.
55
+ const chain = [
56
+ '1.0.0-alpha',
57
+ '1.0.0-alpha.1',
58
+ '1.0.0-alpha.beta',
59
+ '1.0.0-beta',
60
+ '1.0.0-beta.2',
61
+ '1.0.0-beta.11',
62
+ '1.0.0-rc.1',
63
+ '1.0.0',
64
+ ]
65
+ for (let i = 0; i < chain.length - 1; i += 1) {
66
+ assert.equal(compareSemver(chain[i], chain[i + 1]), -1, chain[i] + ' < ' + chain[i + 1])
67
+ assert.equal(compareSemver(chain[i + 1], chain[i]), 1, chain[i + 1] + ' > ' + chain[i])
68
+ assert.equal(isNewer(chain[i + 1], chain[i]), true)
69
+ assert.equal(isNewer(chain[i], chain[i + 1]), false)
70
+ }
71
+ // Numeric identifiers compare numerically (not lexically): 2 < 11.
72
+ assert.equal(compareSemver('1.0.0-beta.2', '1.0.0-beta.11'), -1)
73
+ // Build metadata is ignored for precedence.
74
+ assert.equal(compareSemver('1.0.0+build.9', '1.0.0+sha.1'), 0)
75
+ assert.equal(compareSemver('1.0.0', 'v1.0.0'), 0)
76
+ // Triplet ordering.
77
+ assert.equal(compareSemver('0.2.1', '0.2.0'), 1)
78
+ assert.equal(compareSemver('5.2.0', '5.10.0'), -1)
79
+ })
80
+
81
+ test('compareSemver/isNewer never throw on invalid input', () => {
82
+ assert.equal(compareSemver('nope', '1.0.0'), 0)
83
+ assert.equal(compareSemver('1.0.0', undefined), 0)
84
+ assert.equal(isNewer('5.2.0', ''), false, 'unknown current version never claims an update')
85
+ assert.equal(isNewer('', '5.1.0'), false)
86
+ assert.equal(isNewer('garbage', '5.1.0'), false)
87
+ assert.equal(isNewer(undefined, undefined), false)
88
+ // compareSemver also takes already-parsed objects.
89
+ assert.equal(compareSemver(parseSemver('1.0.0'), '1.0.0'), 0)
90
+ })
91
+
92
+ test('isNewer is prerelease-aware for this repo dev builds', () => {
93
+ // A dev build is OLDER than the release with the same base version ...
94
+ assert.equal(isNewer('5.2.0', '5.2.0-dev.1'), true)
95
+ assert.equal(isNewer('5.2.0-dev.2', '5.2.0-dev.1'), true)
96
+ assert.equal(isNewer('5.2.0-dev.1', '5.2.0'), false)
97
+ // ... and 5.2.0-dev.1 is NEWER than the previous 5.1.x release line.
98
+ assert.equal(isNewer('5.2.0-dev.1', '5.1.0'), true)
99
+ // Equal versions are never an update.
100
+ assert.equal(isNewer('5.2.0', '5.2.0'), false)
101
+ assert.equal(isNewer('5.2.0-dev.1', '5.2.0-dev.1'), false)
102
+ })
103
+
104
+ // ── the dsh >= 0.2.1 gate ─────────────────────────────────────────────────
105
+
106
+ test('atLeastBase compares the numeric triplet and ignores prerelease', () => {
107
+ assert.equal(atLeastBase('0.2.1-rc.2', DSH_MODERN_BASE), true, 'a 0.2.1 prerelease counts as 0.2.1')
108
+ assert.equal(atLeastBase('0.2.1', DSH_MODERN_BASE), true)
109
+ assert.equal(atLeastBase('0.2.2', DSH_MODERN_BASE), true)
110
+ assert.equal(atLeastBase('1.0.0', DSH_MODERN_BASE), true)
111
+ assert.equal(atLeastBase('0.2.0-rc.2', DSH_MODERN_BASE), false)
112
+ assert.equal(atLeastBase('0.1.9', DSH_MODERN_BASE), false)
113
+ assert.equal(atLeastBase('', DSH_MODERN_BASE), false)
114
+ assert.equal(atLeastBase('unknown', DSH_MODERN_BASE), false)
115
+ })
116
+
117
+ test('dshIsModern: resolved version wins; unresolvable is modern only on desktop', () => {
118
+ assert.equal(dshIsModern({ version: '0.2.1-rc.2', desktop: false }), true)
119
+ assert.equal(dshIsModern({ version: '0.2.1-rc.2', desktop: true }), true)
120
+ assert.equal(dshIsModern({ version: '0.1.6-alpha.2', desktop: true }), false, 'an old resolved version is old even on desktop')
121
+ assert.equal(dshIsModern({ version: '', desktop: true }), true, 'desktop with an unresolvable version stays modern')
122
+ assert.equal(dshIsModern({ version: '', desktop: false }), false, 'unknown web version => legacy UI')
123
+ assert.equal(dshIsModern({}), false)
124
+ assert.equal(dshIsModern(undefined), false)
125
+ })
126
+
127
+ // ── runtime discovery ─────────────────────────────────────────────────────
128
+
129
+ function writePkg(dir, name, version) {
130
+ mkdirSync(dir, { recursive: true })
131
+ writeFileSync(join(dir, 'package.json'), JSON.stringify({ name, version }, null, 2))
132
+ }
133
+
134
+ test('walkDshVersion finds the installed dsh package above a fake argv entry', () => {
135
+ const root = mkdtempSync(join(tmpdir(), 'dsh-meta-walk-'))
136
+ try {
137
+ // <root>/app is the dsh package; the entry point is <root>/app/lib/cli.js.
138
+ writePkg(join(root, 'app'), '@deepseek-ai/dsh', '0.2.1-rc.2')
139
+ const entry = join(root, 'app', 'lib', 'cli.js')
140
+ mkdirSync(dirname(entry), { recursive: true })
141
+ writeFileSync(entry, '// fake entry\n')
142
+ assert.equal(walkDshVersion(entry), '0.2.1-rc.2')
143
+
144
+ // A directory entry works too (some loaders pass one).
145
+ assert.equal(walkDshVersion(join(root, 'app', 'lib')), '0.2.1-rc.2')
146
+
147
+ // A nearer, non-dsh package.json is skipped rather than accepted.
148
+ writePkg(join(root, 'app', 'lib'), 'some-other-package', '9.9.9')
149
+ assert.equal(walkDshVersion(entry), '0.2.1-rc.2')
150
+
151
+ // An unresolvable path simply yields ''.
152
+ assert.equal(walkDshVersion(join(root, 'nope', 'nested', 'cli.js')), '')
153
+ assert.equal(walkDshVersion(''), '')
154
+ assert.equal(walkDshVersion(undefined), '')
155
+ } finally {
156
+ rmSync(root, { recursive: true, force: true })
157
+ }
158
+ })
159
+
160
+ test('walkDshVersion honours the maxDepth budget', () => {
161
+ const root = mkdtempSync(join(tmpdir(), 'dsh-meta-depth-'))
162
+ try {
163
+ writePkg(root, '@deepseek-ai/dsh', '0.2.1')
164
+ // 10 directories between the entry file and the package root: beyond the
165
+ // default 8-level walk, so the version must NOT be found.
166
+ let dir = root
167
+ const parts = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j']
168
+ for (const part of parts) dir = join(dir, part)
169
+ mkdirSync(dir, { recursive: true })
170
+ const entry = join(dir, 'cli.js')
171
+ writeFileSync(entry, '// fake entry\n')
172
+ assert.equal(walkDshVersion(entry), '')
173
+ assert.equal(walkDshVersion(entry, { maxDepth: 12 }), '0.2.1')
174
+ // A package.json whose version is not semver is ignored.
175
+ const other = mkdtempSync(join(tmpdir(), 'dsh-meta-badver-'))
176
+ try {
177
+ writePkg(join(other, 'pkg'), '@deepseek-ai/dsh', 'not-a-version')
178
+ assert.equal(walkDshVersion(join(other, 'pkg', 'bin.js')), '')
179
+ } finally {
180
+ rmSync(other, { recursive: true, force: true })
181
+ }
182
+ } finally {
183
+ rmSync(root, { recursive: true, force: true })
184
+ }
185
+ })
186
+
187
+ test('walkDshVersion recognises every documented host package name', () => {
188
+ assert.deepEqual([...DSH_PACKAGE_NAMES], [
189
+ '@deepseek-ai/dsh',
190
+ '@deepseek-ai/dsh-desktop-runtime',
191
+ '@deepseek-ai/dsh-desktop',
192
+ ])
193
+ for (const name of DSH_PACKAGE_NAMES) {
194
+ const root = mkdtempSync(join(tmpdir(), 'dsh-meta-name-'))
195
+ try {
196
+ writePkg(root, name, '0.2.1')
197
+ assert.equal(walkDshVersion(join(root, 'lib', 'bin.js')), '0.2.1', name)
198
+ } finally {
199
+ rmSync(root, { recursive: true, force: true })
200
+ }
201
+ }
202
+ })
203
+
204
+ test('readPluginVersion equals this package.json version and is cached', () => {
205
+ const pkg = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8'))
206
+ assert.equal(PLUGIN_NAME, 'dsh-dbhub-live')
207
+ assert.equal(readPluginVersion(), pkg.version)
208
+ assert.equal(readPluginVersion(), pkg.version, 'the value is stable across calls')
209
+ // The package.json version must itself be semver, otherwise the update check
210
+ // would be comparing against an unknown version forever.
211
+ assert.ok(parseSemver(readPluginVersion()), 'package.json version must be semver')
212
+ })
213
+
214
+ test('detectDshRuntime reports a coherent runtime (never throws)', () => {
215
+ const runtime = detectDshRuntime()
216
+ assert.equal(typeof runtime.version, 'string')
217
+ assert.equal(typeof runtime.desktop, 'boolean')
218
+ // This test process runs under plain node (no Electron), so unless the shell
219
+ // exports DSH_CLIENT_VERSION the runtime must be web.
220
+ if (!process.versions.electron && !process.env.DSH_CLIENT_VERSION) {
221
+ assert.equal(runtime.desktop, false)
222
+ assert.equal(runtime.version, '')
223
+ }
224
+ if (runtime.version) assert.ok(parseSemver(runtime.version), 'a resolved version is always semver')
225
+ })
226
+
227
+ test('versionMeta publishes the four view fields', () => {
228
+ const meta = versionMeta()
229
+ assert.deepEqual(Object.keys(meta).sort(), ['dshModern', 'dshVersion', 'pluginVersion', 'runtime'])
230
+ assert.equal(meta.pluginVersion, readPluginVersion())
231
+ assert.ok(meta.runtime === 'web' || meta.runtime === 'desktop')
232
+ assert.equal(meta.dshModern, dshIsModern({ version: meta.dshVersion, desktop: meta.runtime === 'desktop' }))
233
+ assert.equal(typeof meta.dshModern, 'boolean')
234
+ })