dsh-dbhub-live 5.2.0-dev.3 → 6.0.0-rc.1

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.
Files changed (58) hide show
  1. package/README.en.md +36 -33
  2. package/README.md +37 -34
  3. package/lib/adhoc.mjs +108 -12
  4. package/lib/capability.mjs +97 -13
  5. package/lib/client.js +705 -315
  6. package/lib/config.mjs +490 -99
  7. package/lib/i18n.mjs +68 -2
  8. package/lib/index.mjs +386 -260
  9. package/lib/mcp.mjs +245 -49
  10. package/lib/meta.mjs +275 -267
  11. package/lib/options.mjs +11 -33
  12. package/lib/policy.mjs +92 -92
  13. package/lib/runtime.mjs +80 -11
  14. package/lib/state.mjs +112 -117
  15. package/lib/toml.mjs +10 -0
  16. package/lib/tools.mjs +118 -24
  17. package/lib/update.mjs +254 -244
  18. package/package.json +4 -20
  19. package/AGENTS.md +0 -116
  20. package/doc/ARCHITECTURE.md +0 -108
  21. package/doc/CLIENT.md +0 -124
  22. package/doc/DESENSITIZE.md +0 -56
  23. package/doc/DEVELOPMENT.md +0 -139
  24. package/doc/IMPACT-MATRIX.md +0 -36
  25. package/doc/RELEASE.md +0 -93
  26. package/doc/REQUIREMENTS.md +0 -177
  27. package/doc/images/chat-query.png +0 -0
  28. package/doc/images/row-editor.png +0 -0
  29. package/doc/images/settings-page.png +0 -0
  30. package/scripts/desensitize-check.mjs +0 -331
  31. package/scripts/desensitize.config.mjs +0 -255
  32. package/scripts/docs-check.mjs +0 -214
  33. package/scripts/release-preflight.mjs +0 -180
  34. package/scripts/render-check.mjs +0 -1366
  35. package/test/adhoc-toml.test.mjs +0 -365
  36. package/test/auto-dsn.test.mjs +0 -126
  37. package/test/bridge.test.mjs +0 -227
  38. package/test/capability.test.mjs +0 -231
  39. package/test/client-format.test.mjs +0 -515
  40. package/test/configure-options.test.mjs +0 -244
  41. package/test/configure-ssh.test.mjs +0 -232
  42. package/test/copy-from.test.mjs +0 -155
  43. package/test/env-options.test.mjs +0 -456
  44. package/test/i18n.test.mjs +0 -37
  45. package/test/init.test.mjs +0 -163
  46. package/test/meta.test.mjs +0 -234
  47. package/test/options.test.mjs +0 -160
  48. package/test/policy.test.mjs +0 -132
  49. package/test/resolve-source.test.mjs +0 -82
  50. package/test/resolve-workspace.test.mjs +0 -115
  51. package/test/row-cache.test.mjs +0 -237
  52. package/test/ssh-probe.test.mjs +0 -834
  53. package/test/state.test.mjs +0 -85
  54. package/test/test-op-fastpath.test.mjs +0 -208
  55. package/test/toml.test.mjs +0 -352
  56. package/test/update.test.mjs +0 -270
  57. package/test/util.test.mjs +0 -275
  58. package/test/zero-knowledge.test.mjs +0 -121
@@ -12,12 +12,43 @@
12
12
  // Detection is deliberately file-based (no extra process spawn): the executable
13
13
  // we resolved lives inside a package directory (`…/node_modules/.bin/dbhub.cmd`
14
14
  // next to `…/node_modules/@bytebase/dbhub/package.json`) or under mise's
15
- // `npm-bytebase-dbhub/<version>/` layout, and both carry the version.
15
+ // `npm-bytebase-dbhub/<version>/` layout, or it is the classic global layout
16
+ // (`<prefix>/bin/dbhub` + `<prefix>/lib/node_modules/@bytebase/dbhub`), and all
17
+ // of them carry the version. An UNKNOWN version still allows both features
18
+ // (R62); the point of covering the standard layouts is that "unknown" stays the
19
+ // exception instead of being the default answer.
16
20
  // Pure module: `node:fs` + `node:path` only, no Cordis service.
17
21
 
18
- import { existsSync, readFileSync, statSync } from 'node:fs'
22
+ import { existsSync, readFileSync, statSync, realpathSync } from 'node:fs'
19
23
  import { dirname, join, resolve } from 'node:path'
20
24
 
25
+ // Process-wide detection cache, keyed by `<absolute exe path>\0<mtimeMs>`. The
26
+ // answer for one executable is immutable for the life of the process, while a
27
+ // TOML call used to re-walk up to 18 candidate manifests with readFileSync on
28
+ // EVERY tool call (the guard runs before each read-only / tunnelled spawn). The
29
+ // mtime keeps an in-place upgrade (`npm install` over the old prefix) from
30
+ // serving a stale version, and one statSync is microseconds next to that walk.
31
+ const DETECTION_CACHE = new Map()
32
+
33
+ // Production reads the real fs; tests swap single primitives to COUNT the probes
34
+ // (the same seam config.mjs uses for its storage writers). Nothing here reads a
35
+ // hook unless a test installed one. `realpathSync` resolves a shim that is a
36
+ // SYMLINK (nvm / volta / pnpm, and npm -g's `bin/dbhub` link) to the package
37
+ // directory it really lives in.
38
+ const NODE_FS = { existsSync, readFileSync, statSync, realpathSync }
39
+ let fsHooks = NODE_FS
40
+
41
+ /**
42
+ * Replace a subset of the file primitives used by the version probe.
43
+ * @param hooks - `{ existsSync?, readFileSync?, statSync?, realpathSync? }`, or
44
+ * `null` to restore node:fs. Unlisted primitives keep the real implementation.
45
+ * The detection cache is dropped so a swapped fs can never be served a hit.
46
+ */
47
+ export function setFsHooks(hooks) {
48
+ fsHooks = hooks ? { ...NODE_FS, ...hooks } : NODE_FS
49
+ DETECTION_CACHE.clear()
50
+ }
51
+
21
52
  /** Minimum dbhub version whose TOML dialect this plugin generates. */
22
53
  export const DBHUB_MIN_VERSION = '1.4.0'
23
54
  /** Versions below this predate the TOML tool model entirely. */
@@ -62,11 +93,27 @@ export function versionFromMisePath(exe) {
62
93
  return parseVersion(m[1]) ? m[1] : ''
63
94
  }
64
95
 
96
+ /**
97
+ * The real path of an executable, or the given path when it cannot be resolved.
98
+ *
99
+ * A dbhub reached through a link must be walked UP from its TARGET: npm -g puts
100
+ * `bin/dbhub` as a symlink into `lib/node_modules`, and nvm / volta / pnpm shim
101
+ * the same way, so the link's own directory contains no manifest at all.
102
+ */
103
+ function realExe(raw) {
104
+ try {
105
+ return fsHooks.realpathSync(raw)
106
+ } catch (e) {
107
+ return raw
108
+ }
109
+ }
110
+
65
111
  /**
66
112
  * Walk up from the executable (bounded) looking for the owning package.json and
67
113
  * read its version. Works for the auto-managed install
68
- * (`<RUNTIME_DIR>/node_modules/@bytebase/dbhub/package.json`) and for any npm /
69
- * pnpm / mise layout that keeps the manifest above the shim.
114
+ * (`<RUNTIME_DIR>/node_modules/@bytebase/dbhub/package.json`), for any npm /
115
+ * pnpm / mise layout that keeps the manifest above the shim, and for the
116
+ * classic global layout (`<prefix>/bin/dbhub` + `<prefix>/lib/node_modules/…`).
70
117
  * @returns a version string, or `''`.
71
118
  */
72
119
  export function versionFromPackageJson(exe) {
@@ -74,25 +121,36 @@ export function versionFromPackageJson(exe) {
74
121
  if (!raw) return ''
75
122
  let dir
76
123
  try {
77
- dir = dirname(resolve(raw))
124
+ dir = dirname(realExe(resolve(raw)))
78
125
  } catch (e) {
79
126
  return ''
80
127
  }
81
128
  for (let i = 0; i < 6 && dir; i++) {
82
- // Three layouts matter, and all of them must be probed at EVERY level:
129
+ // Every layout matters, and all of them must be probed at EVERY level:
83
130
  // · `<pkg>/package.json` — the mise shim package that pins dbhub;
84
- // · `<dir>/node_modules/@bytebase/dbhub/package.json` — what
131
+ // · `<dir>/node_modules/{@bytebase/dbhub,dbhub}/package.json` — what
85
132
  // `npm install @bytebase/dbhub --prefix <dir>` produces, i.e. the
86
- // DEFAULT auto-managed install (and `npm -g` too);
87
- // · `<dir>/node_modules/dbhub/package.json` — an unscoped install.
133
+ // DEFAULT auto-managed install;
134
+ // · `<dir>/lib/node_modules/…` — the npm GLOBAL / nvm / volta prefix
135
+ // (`<prefix>/bin/dbhub` next to `<prefix>/lib/node_modules/…`). This
136
+ // candidate is what keeps a default `npm i -g` install from resolving
137
+ // to '' : an unknown version is ALLOWED by design (R62), so the
138
+ // `< 1.0.0` refusal was unreachable and a `[[tools]] readonly` TOML was
139
+ // handed to a dbhub that ignores it — looking read-only while every
140
+ // write still went through;
141
+ // · `<dir>/../../lib/node_modules/…` — the same prefix seen from a shim
142
+ // that sits one directory deeper (`<prefix>/bin/<sub>/dbhub`).
88
143
  for (const candidate of [
89
144
  join(dir, 'package.json'),
90
145
  join(dir, 'node_modules', '@bytebase', 'dbhub', 'package.json'),
91
146
  join(dir, 'node_modules', 'dbhub', 'package.json'),
147
+ join(dir, 'lib', 'node_modules', '@bytebase', 'dbhub', 'package.json'),
148
+ join(dir, 'lib', 'node_modules', 'dbhub', 'package.json'),
149
+ join(dir, '..', '..', 'lib', 'node_modules', '@bytebase', 'dbhub', 'package.json'),
92
150
  ]) {
93
151
  try {
94
- if (!existsSync(candidate) || !statSync(candidate).isFile()) continue
95
- const doc = JSON.parse(readFileSync(candidate, 'utf8'))
152
+ if (!fsHooks.existsSync(candidate) || !fsHooks.statSync(candidate).isFile()) continue
153
+ const doc = JSON.parse(fsHooks.readFileSync(candidate, 'utf8'))
96
154
  const version = parseVersion(doc && doc.version) ? String(doc.version) : ''
97
155
  if (!version) continue
98
156
  // Only trust a manifest that IS dbhub: an unrelated package.json above
@@ -139,19 +197,45 @@ export function capabilitiesOf(rawVersion) {
139
197
  return { version, readonlyTools: true, ssh: true, warning: '' }
140
198
  }
141
199
 
200
+ /**
201
+ * Detection cache key: the path plus the executable's mtime (0 when it cannot be
202
+ * stat'ed, which keeps the key stable for a layout whose shim does not exist).
203
+ */
204
+ function cacheKeyOf(raw) {
205
+ let stamp = 0
206
+ if (raw) {
207
+ try {
208
+ stamp = Number(fsHooks.statSync(raw).mtimeMs) || 0
209
+ } catch (e) {
210
+ stamp = 0
211
+ }
212
+ }
213
+ return raw + '\u0000' + String(stamp)
214
+ }
215
+
142
216
  /**
143
217
  * Detect the capabilities of one resolved dbhub executable.
144
218
  * Never throws and never blocks: an unresolvable version is reported as
145
219
  * `unknown` and the features stay enabled (the caller only *uses* this on
146
220
  * read-only / SSH environments, and an genuine incompatibility surfaces as a
147
221
  * clear dbhub error).
222
+ *
223
+ * Cached per executable (see DETECTION_CACHE); a copy is returned so a caller can
224
+ * never poison the cache.
148
225
  * @param exe - resolved dbhub executable path (may be undefined at boot).
149
226
  * @returns `{ version, readonlyTools, ssh, warning }`.
150
227
  */
151
228
  export function detectDbhubCapabilities(exe) {
229
+ const raw = String(exe === undefined || exe === null ? '' : exe).trim()
230
+ const key = cacheKeyOf(raw)
231
+ const hit = DETECTION_CACHE.get(key)
232
+ if (hit) return { ...hit }
233
+ let value
152
234
  try {
153
- return capabilitiesOf(detectVersion(exe))
235
+ value = capabilitiesOf(detectVersion(raw))
154
236
  } catch (e) {
155
- return { version: '', readonlyTools: true, ssh: true, warning: 'unknown' }
237
+ value = { version: '', readonlyTools: true, ssh: true, warning: 'unknown' }
156
238
  }
239
+ DETECTION_CACHE.set(key, value)
240
+ return { ...value }
157
241
  }