dsh-dbhub-live 5.0.1 → 5.2.0-dev.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/AGENTS.md +95 -209
- package/README.en.md +251 -169
- package/README.md +251 -169
- package/doc/ARCHITECTURE.md +108 -0
- package/doc/CLIENT.md +124 -0
- package/doc/DESENSITIZE.md +56 -0
- package/doc/DEVELOPMENT.md +139 -0
- package/doc/IMPACT-MATRIX.md +36 -0
- package/doc/RELEASE.md +93 -0
- package/doc/REQUIREMENTS.md +177 -105
- package/doc/images/chat-query.png +0 -0
- package/doc/images/row-editor.png +0 -0
- package/doc/images/settings-page.png +0 -0
- package/lib/adhoc.mjs +412 -34
- package/lib/capability.mjs +157 -0
- package/lib/client.js +1243 -80
- package/lib/config.mjs +370 -21
- package/lib/i18n.mjs +119 -4
- package/lib/index.mjs +401 -43
- package/lib/mcp.mjs +496 -391
- package/lib/meta.mjs +267 -0
- package/lib/options.mjs +25 -5
- package/lib/policy.mjs +92 -0
- package/lib/toml.mjs +188 -0
- package/lib/tools.mjs +1093 -827
- package/lib/update.mjs +244 -0
- package/package.json +34 -10
- package/scripts/desensitize-check.mjs +10 -3
- package/scripts/desensitize.config.mjs +255 -237
- package/scripts/docs-check.mjs +214 -0
- package/scripts/release-preflight.mjs +180 -0
- package/scripts/render-check.mjs +1366 -843
- package/test/adhoc-toml.test.mjs +365 -0
- package/test/bridge.test.mjs +227 -185
- package/test/capability.test.mjs +231 -0
- package/test/client-format.test.mjs +515 -352
- package/test/configure-options.test.mjs +244 -0
- package/test/configure-ssh.test.mjs +232 -0
- package/test/copy-from.test.mjs +155 -0
- package/test/env-options.test.mjs +456 -0
- package/test/meta.test.mjs +234 -0
- package/test/options.test.mjs +160 -123
- package/test/policy.test.mjs +132 -0
- package/test/row-cache.test.mjs +237 -138
- package/test/ssh-probe.test.mjs +834 -0
- package/test/test-op-fastpath.test.mjs +208 -0
- package/test/toml.test.mjs +352 -0
- package/test/update.test.mjs +270 -0
- package/test/util.test.mjs +17 -0
- package/test/zero-knowledge.test.mjs +120 -78
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
// Unit tests for lib/capability.mjs — the dbhub version / capability guard.
|
|
2
|
+
//
|
|
3
|
+
// capability.mjs is a PURE module (node:fs + node:path only, it does not import
|
|
4
|
+
// config.mjs), so unlike the storage-bound lib tests it needs no DSH_HOME.
|
|
5
|
+
//
|
|
6
|
+
// Fixtures obey the desensitization convention: loopback / RFC 5737 hosts and
|
|
7
|
+
// placeholder secrets only. The dbhub executable itself is never created — the
|
|
8
|
+
// detector is deliberately file-based and only reads package.json manifests.
|
|
9
|
+
|
|
10
|
+
import test from 'node:test'
|
|
11
|
+
import assert from 'node:assert/strict'
|
|
12
|
+
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs'
|
|
13
|
+
import { tmpdir } from 'node:os'
|
|
14
|
+
import { join } from 'node:path'
|
|
15
|
+
|
|
16
|
+
const cap = await import('../lib/capability.mjs')
|
|
17
|
+
|
|
18
|
+
const tempDirs = []
|
|
19
|
+
|
|
20
|
+
function tempDir(tag) {
|
|
21
|
+
const dir = mkdtempSync(join(tmpdir(), 'dsh-dbhub-cap-' + tag + '-'))
|
|
22
|
+
tempDirs.push(dir)
|
|
23
|
+
return dir
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* The layout runtime.mjs produces with
|
|
28
|
+
* `npm install @bytebase/dbhub --prefix <RUNTIME_DIR>`:
|
|
29
|
+
* <dir>/node_modules/.bin/dbhub.cmd
|
|
30
|
+
* <dir>/node_modules/@bytebase/dbhub/package.json
|
|
31
|
+
* The shim file itself need not exist (and is never read).
|
|
32
|
+
*/
|
|
33
|
+
function managedInstall(version) {
|
|
34
|
+
const dir = tempDir('managed')
|
|
35
|
+
const pkgDir = join(dir, 'node_modules', '@bytebase', 'dbhub')
|
|
36
|
+
mkdirSync(pkgDir, { recursive: true })
|
|
37
|
+
writeFileSync(join(pkgDir, 'package.json'), JSON.stringify({ name: '@bytebase/dbhub', version }))
|
|
38
|
+
return { dir, exe: join(dir, 'node_modules', '.bin', 'dbhub.cmd') }
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** The mise-install path form; the shim file need not exist either. */
|
|
42
|
+
function miseExe(version) {
|
|
43
|
+
return join(tempDir('mise'), 'npm-bytebase-dbhub', version, 'node_modules', '.bin', 'dbhub.cmd')
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
test.after(() => {
|
|
47
|
+
for (const dir of tempDirs) rmSync(dir, { recursive: true, force: true })
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
// ── pure version helpers ──────────────────────────────────────────────────
|
|
51
|
+
|
|
52
|
+
test('parseVersion accepts a leading v, prerelease/build suffixes and a missing patch', () => {
|
|
53
|
+
assert.deepEqual(cap.parseVersion('1.2.3'), { major: 1, minor: 2, patch: 3 })
|
|
54
|
+
assert.deepEqual(cap.parseVersion('v1.2.3'), { major: 1, minor: 2, patch: 3 })
|
|
55
|
+
assert.deepEqual(cap.parseVersion('1.4.0-beta.1'), { major: 1, minor: 4, patch: 0 })
|
|
56
|
+
assert.deepEqual(cap.parseVersion('1.4.0+build.9'), { major: 1, minor: 4, patch: 0 })
|
|
57
|
+
assert.deepEqual(cap.parseVersion('1.2'), { major: 1, minor: 2, patch: 0 })
|
|
58
|
+
assert.deepEqual(cap.parseVersion(' v0.9.0 '), { major: 0, minor: 9, patch: 0 })
|
|
59
|
+
// only a LEADING numeric triplet counts — a version buried in text is not one
|
|
60
|
+
for (const bad of ['', 'garbage', 'dbhub 1.4.0', 'v', 'x1.2.3', '1', '1.x', undefined, null, 42, {}]) {
|
|
61
|
+
assert.equal(cap.parseVersion(bad), null, 'expected null for ' + String(bad))
|
|
62
|
+
}
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
test('compareVersions orders by major/minor/patch and atLeast is inclusive', () => {
|
|
66
|
+
const p = cap.parseVersion
|
|
67
|
+
assert.equal(cap.compareVersions(p('1.2.3'), p('1.2.3')), 0)
|
|
68
|
+
assert.ok(cap.compareVersions(p('1.2.3'), p('1.2.4')) < 0)
|
|
69
|
+
assert.ok(cap.compareVersions(p('1.3.0'), p('1.2.9')) > 0)
|
|
70
|
+
assert.ok(cap.compareVersions(p('2.0.0'), p('1.9.9')) > 0)
|
|
71
|
+
// a missing side counts as 0.0.0 (defensive, never throws)
|
|
72
|
+
assert.equal(cap.compareVersions(null, null), 0)
|
|
73
|
+
assert.equal(cap.compareVersions(p('0.0.1'), null), 1)
|
|
74
|
+
assert.equal(cap.compareVersions(null, p('0.0.1')), -1)
|
|
75
|
+
// atLeast: inclusive floor, prerelease suffix ignored, unparsable -> false
|
|
76
|
+
assert.equal(cap.atLeast(p('1.4.0'), '1.4.0'), true)
|
|
77
|
+
assert.equal(cap.atLeast(p('1.4.1'), '1.4.0'), true)
|
|
78
|
+
assert.equal(cap.atLeast(p('1.4.0-beta.1'), '1.4.0'), true)
|
|
79
|
+
assert.equal(cap.atLeast(p('1.3.9'), '1.4.0'), false)
|
|
80
|
+
assert.equal(cap.atLeast(p('0.9.0'), '1.0.0'), false)
|
|
81
|
+
assert.equal(cap.atLeast(null, '1.4.0'), false)
|
|
82
|
+
assert.equal(cap.atLeast(p('1.4.0'), 'not-a-version'), false)
|
|
83
|
+
// the documented floors
|
|
84
|
+
assert.equal(cap.DBHUB_MIN_VERSION, '1.4.0')
|
|
85
|
+
assert.equal(cap.DBHUB_TOML_SINCE, '1.0.0')
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
test('capabilitiesOf maps the version matrix onto the two generated features', () => {
|
|
89
|
+
// < 1.0.0 predates the TOML tool model entirely -> refuse both features
|
|
90
|
+
assert.deepEqual(cap.capabilitiesOf('0.9.0'), {
|
|
91
|
+
version: '0.9.0', readonlyTools: false, ssh: false, warning: 'tooOld',
|
|
92
|
+
})
|
|
93
|
+
assert.equal(cap.capabilitiesOf('0.0.1').warning, 'tooOld')
|
|
94
|
+
// 1.0.0-1.3.x: allowed but never measured -> warn
|
|
95
|
+
assert.deepEqual(cap.capabilitiesOf('1.2.0'), {
|
|
96
|
+
version: '1.2.0', readonlyTools: true, ssh: true, warning: 'untested',
|
|
97
|
+
})
|
|
98
|
+
assert.equal(cap.capabilitiesOf('1.0.0').warning, 'untested')
|
|
99
|
+
assert.equal(cap.capabilitiesOf('1.3.99').warning, 'untested')
|
|
100
|
+
// >= 1.4.0: the dialect this plugin was measured against -> no warning
|
|
101
|
+
assert.deepEqual(cap.capabilitiesOf('1.4.0'), {
|
|
102
|
+
version: '1.4.0', readonlyTools: true, ssh: true, warning: '',
|
|
103
|
+
})
|
|
104
|
+
assert.equal(cap.capabilitiesOf('1.5.2').warning, '')
|
|
105
|
+
assert.equal(cap.capabilitiesOf('v2.0.0').warning, '')
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
test('capabilitiesOf reports unknown (features ON) for an unparsable version', () => {
|
|
109
|
+
for (const raw of ['', 'garbage', undefined, null]) {
|
|
110
|
+
const c = cap.capabilitiesOf(raw)
|
|
111
|
+
assert.equal(c.readonlyTools, true)
|
|
112
|
+
assert.equal(c.ssh, true)
|
|
113
|
+
assert.equal(c.warning, 'unknown')
|
|
114
|
+
}
|
|
115
|
+
// the raw value is echoed back as a string so the UI can show what was seen
|
|
116
|
+
assert.equal(cap.capabilitiesOf('').version, '')
|
|
117
|
+
assert.equal(cap.capabilitiesOf(undefined).version, '')
|
|
118
|
+
assert.equal(cap.capabilitiesOf('garbage').version, 'garbage')
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
// ── executable-path detection ─────────────────────────────────────────────
|
|
122
|
+
|
|
123
|
+
test('a managed install resolves the version from its package.json', () => {
|
|
124
|
+
const { exe } = managedInstall('1.4.0')
|
|
125
|
+
const detected = cap.detectVersion(exe)
|
|
126
|
+
const c = cap.detectDbhubCapabilities(exe)
|
|
127
|
+
// KNOWN GAP, reported rather than worked around: versionFromPackageJson only
|
|
128
|
+
// probes `<dir>/package.json` and `<dir>/node_modules/dbhub/package.json`, so
|
|
129
|
+
// the SCOPED manifest of this plugin's own managed install
|
|
130
|
+
// (`<dir>/node_modules/@bytebase/dbhub/package.json`, see runtime.mjs) is
|
|
131
|
+
// never read. Fix: probe `join(dir, 'node_modules', '@bytebase', 'dbhub',
|
|
132
|
+
// 'package.json')` as well.
|
|
133
|
+
assert.equal(
|
|
134
|
+
detected,
|
|
135
|
+
'1.4.0',
|
|
136
|
+
'managed install not detected (see the KNOWN GAP note above)',
|
|
137
|
+
)
|
|
138
|
+
assert.equal(c.version, '1.4.0')
|
|
139
|
+
assert.equal(c.readonlyTools, true)
|
|
140
|
+
assert.equal(c.ssh, true)
|
|
141
|
+
assert.equal(c.warning, '')
|
|
142
|
+
})
|
|
143
|
+
|
|
144
|
+
test('a mise layout resolves the version from the path segment', () => {
|
|
145
|
+
const exe = miseExe('1.2.0')
|
|
146
|
+
const c = cap.detectDbhubCapabilities(exe)
|
|
147
|
+
assert.equal(c.version, '1.2.0')
|
|
148
|
+
assert.equal(c.readonlyTools, true)
|
|
149
|
+
assert.equal(c.ssh, true)
|
|
150
|
+
assert.equal(c.warning, 'untested')
|
|
151
|
+
assert.equal(cap.detectVersion(miseExe('1.4.0')), '1.4.0')
|
|
152
|
+
assert.equal(cap.detectDbhubCapabilities(miseExe('1.4.0')).warning, '')
|
|
153
|
+
// a non-version segment is not a version
|
|
154
|
+
assert.equal(cap.versionFromMisePath(miseExe('latest')), '')
|
|
155
|
+
assert.equal(cap.versionFromMisePath('/usr/local/bin/dbhub'), '')
|
|
156
|
+
})
|
|
157
|
+
|
|
158
|
+
test('a pre-1.0 install refuses BOTH features with an upgrade hint', () => {
|
|
159
|
+
const c = cap.detectDbhubCapabilities(miseExe('0.9.0'))
|
|
160
|
+
assert.equal(c.version, '0.9.0')
|
|
161
|
+
assert.equal(c.readonlyTools, false)
|
|
162
|
+
assert.equal(c.ssh, false)
|
|
163
|
+
assert.equal(c.warning, 'tooOld')
|
|
164
|
+
})
|
|
165
|
+
|
|
166
|
+
test('an unresolvable executable reports unknown and never throws', () => {
|
|
167
|
+
// Deep enough that the bounded walk-up stays inside the temp dir, and with no
|
|
168
|
+
// package.json and no mise segment anywhere above it.
|
|
169
|
+
const deep = join(tempDir('unknown'), 'a', 'b', 'c', 'd', 'e', 'node_modules', '.bin')
|
|
170
|
+
mkdirSync(deep, { recursive: true })
|
|
171
|
+
const exe = join(deep, 'dbhub.cmd')
|
|
172
|
+
assert.equal(cap.detectVersion(exe), '')
|
|
173
|
+
const c = cap.detectDbhubCapabilities(exe)
|
|
174
|
+
assert.equal(c.version, '')
|
|
175
|
+
assert.equal(c.readonlyTools, true)
|
|
176
|
+
assert.equal(c.ssh, true)
|
|
177
|
+
assert.equal(c.warning, 'unknown')
|
|
178
|
+
// the defensive surface: unresolvable PATHS must not throw either
|
|
179
|
+
for (const bad of [join(deep, 'nope.cmd'), join(deep, 'gone', 'dbhub.cmd'), exe.toUpperCase()]) {
|
|
180
|
+
const safe = cap.detectDbhubCapabilities(bad)
|
|
181
|
+
assert.equal(safe.readonlyTools, true)
|
|
182
|
+
assert.equal(safe.ssh, true)
|
|
183
|
+
assert.equal(safe.warning, 'unknown')
|
|
184
|
+
}
|
|
185
|
+
// REPORTED (not asserted here, no spec for it): a bare relative name is NOT
|
|
186
|
+
// an unresolvable path for this detector. `detectDbhubCapabilities('garbage')`
|
|
187
|
+
// resolves it against process.cwd() and then accepts any manifest whose name
|
|
188
|
+
// merely matches /dbhub/i — including this plugin's OWN package.json
|
|
189
|
+
// (`dsh-dbhub-live`), whose version it then reports with warning ''.
|
|
190
|
+
// Measured here: detectDbhubCapabilities('garbage') -> the repository's own
|
|
191
|
+
// package.json version, warning ''. The pure `capabilitiesOf('garbage')` is
|
|
192
|
+
// unaffected (asserted above with the empty/unparsable matrix).
|
|
193
|
+
})
|
|
194
|
+
|
|
195
|
+
test('versionFromPackageJson only trusts a dbhub-looking manifest', () => {
|
|
196
|
+
// Nested deep enough that the bounded (6-level) walk-up stays inside the temp
|
|
197
|
+
// dir whatever lives above the OS temp root.
|
|
198
|
+
const base = join(tempDir('foreign'), 'a', 'b', 'c', 'd', 'e', 'f')
|
|
199
|
+
const bin = join(base, 'node_modules', '.bin')
|
|
200
|
+
mkdirSync(bin, { recursive: true })
|
|
201
|
+
// a foreign package.json directly above the shim: its version is NOT dbhub's
|
|
202
|
+
writeFileSync(join(base, 'package.json'), JSON.stringify({ name: 'some-other-tool', version: '9.9.9' }))
|
|
203
|
+
assert.equal(cap.versionFromPackageJson(join(bin, 'dbhub.cmd')), '')
|
|
204
|
+
// the unscoped `node_modules/dbhub` form IS trusted when it is really dbhub
|
|
205
|
+
const unscoped = tempDir('unscoped')
|
|
206
|
+
const inner = join(unscoped, 'node_modules', 'dbhub')
|
|
207
|
+
mkdirSync(inner, { recursive: true })
|
|
208
|
+
writeFileSync(join(inner, 'package.json'), JSON.stringify({ name: 'dbhub', version: '1.4.0' }))
|
|
209
|
+
assert.equal(cap.versionFromPackageJson(join(unscoped, 'node_modules', '.bin', 'dbhub.cmd')), '1.4.0')
|
|
210
|
+
})
|
|
211
|
+
|
|
212
|
+
// ── manifest trust ────────────────────────────────────────────────────────
|
|
213
|
+
|
|
214
|
+
test('an unrelated manifest above the executable never lends its version', () => {
|
|
215
|
+
// Regression: the resolver walks UP from the executable, so without a name
|
|
216
|
+
// check it could read any package.json above it — including this plugin's own
|
|
217
|
+
// (`dsh-dbhub-live`, whose name merely CONTAINS "dbhub") when the executable
|
|
218
|
+
// path is relative or empty.
|
|
219
|
+
const dir = tempDir('foreign-manifest')
|
|
220
|
+
writeFileSync(join(dir, 'package.json'), JSON.stringify({ name: 'dsh-dbhub-live', version: '9.9.9' }))
|
|
221
|
+
const fakeExe = join(dir, 'node_modules', '.bin', 'dbhub.cmd')
|
|
222
|
+
assert.equal(cap.versionFromPackageJson(fakeExe), '', 'a foreign name is not a dbhub version')
|
|
223
|
+
const detected = cap.detectDbhubCapabilities(fakeExe)
|
|
224
|
+
assert.equal(detected.version, '')
|
|
225
|
+
assert.equal(detected.warning, 'unknown')
|
|
226
|
+
assert.equal(detected.readonlyTools, true, 'an unknown version must not disable a working setup')
|
|
227
|
+
// An empty/blank executable can never be resolved into somebody's manifest.
|
|
228
|
+
assert.equal(cap.detectVersion(''), '')
|
|
229
|
+
assert.equal(cap.detectVersion(' '), '')
|
|
230
|
+
assert.equal(cap.detectDbhubCapabilities('').warning, 'unknown')
|
|
231
|
+
})
|