dsh-db-tool 1.8.4 → 1.8.8
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/LICENSE +201 -0
- package/README.md +206 -71
- package/README_EN.md +207 -72
- package/client/client.js +117 -5
- package/dist/adapters/mysql/index.js +36 -22
- package/dist/adapters/redis/index.js +5 -2
- package/dist/adapters/sql-shared/pg-like.js +53 -4
- package/dist/adapters/sqlserver/index.js +99 -21
- package/dist/http/index.js +7 -1
- package/dist/index.js +33 -4
- package/dist/manager.js +63 -2
- package/dist/store/connections.js +3 -1
- package/dist/store/grants.js +46 -10
- package/dist/store/normalize.js +11 -2
- package/package.json +6 -4
- package/patches/dsh-app-boot-route-scoped-hotfix-0.2.1-rc.3.patch +44 -0
- package/scripts/hotfix-core.mjs +4 -2
package/dist/store/grants.js
CHANGED
|
@@ -3,14 +3,46 @@
|
|
|
3
3
|
* 键是 normalizeProjectKey 归一化后的项目路径。
|
|
4
4
|
*/
|
|
5
5
|
import * as path from 'node:path';
|
|
6
|
+
import { normalizeProjectKey } from './normalize.js';
|
|
6
7
|
import { readJson, writeJsonAtomic } from './io.js';
|
|
8
|
+
/** 迁移合并:同键两条授权列表按连接去重,rw 胜出(rw > ro,对齐 check),grantedAt 取较新 */
|
|
9
|
+
function mergeGrantLists(a, b) {
|
|
10
|
+
const map = new Map();
|
|
11
|
+
for (const g of [...a, ...b]) {
|
|
12
|
+
const prev = map.get(g.connId);
|
|
13
|
+
if (!prev) {
|
|
14
|
+
map.set(g.connId, { ...g });
|
|
15
|
+
continue;
|
|
16
|
+
}
|
|
17
|
+
const mode = prev.mode === 'rw' || g.mode === 'rw' ? 'rw' : g.mode;
|
|
18
|
+
map.set(g.connId, { connId: g.connId, mode, grantedAt: prev.grantedAt > g.grantedAt ? prev.grantedAt : g.grantedAt });
|
|
19
|
+
}
|
|
20
|
+
return [...map.values()];
|
|
21
|
+
}
|
|
22
|
+
/** 绝对路径形态的键(盘符开头或 POSIX 根开头):仅这类键参与迁移归一 */
|
|
23
|
+
const PATH_KEY = /^(?:[a-z]:\/|\/)/i;
|
|
7
24
|
export class GrantStore {
|
|
8
25
|
file;
|
|
9
26
|
constructor(dir) {
|
|
10
27
|
this.file = path.join(dir, 'grants.json');
|
|
11
28
|
}
|
|
29
|
+
/**
|
|
30
|
+
* 读入 + 兼容迁移:修复前的 grants.json 键未做 Windows 全路径小写归一
|
|
31
|
+
* (仅盘符小写),同一目录可能存在多个大小写变体键。读入时把「绝对路径
|
|
32
|
+
* 形态」的键统一归一化,变体合并为同一键(同连接多模式时 rw 胜出、
|
|
33
|
+
* grantedAt 取最新,与 check 语义一致),下次 save 自然落盘为新键。
|
|
34
|
+
* 非路径形态的键(历史遗留/测试桩,非 normalizeProjectKey 产出物)
|
|
35
|
+
* 原样保留——resolve 会把它们误改写为绝对路径。归一化幂等,对新格式文件零开销。
|
|
36
|
+
*/
|
|
12
37
|
load() {
|
|
13
|
-
|
|
38
|
+
const raw = readJson(this.file, { grants: {} });
|
|
39
|
+
const grants = {};
|
|
40
|
+
for (const [k, list] of Object.entries(raw.grants ?? {})) {
|
|
41
|
+
const key = PATH_KEY.test(k) ? normalizeProjectKey(k) : k;
|
|
42
|
+
const prev = grants[key];
|
|
43
|
+
grants[key] = prev ? mergeGrantLists(prev, list) : list;
|
|
44
|
+
}
|
|
45
|
+
return { grants };
|
|
14
46
|
}
|
|
15
47
|
save(data) {
|
|
16
48
|
// 0600 对齐 dsh-ssh-tunnel:授权关系本身也是敏感面(泄露项目↔库映射)
|
|
@@ -18,12 +50,14 @@ export class GrantStore {
|
|
|
18
50
|
}
|
|
19
51
|
/** 某项目的全部授权(不含 grantedAt,展示层无需时间戳) */
|
|
20
52
|
grantsFor(projectKey) {
|
|
21
|
-
|
|
53
|
+
const key = normalizeProjectKey(projectKey);
|
|
54
|
+
return (this.load().grants[key] ?? []).map(({ connId, mode }) => ({ connId, mode }));
|
|
22
55
|
}
|
|
23
|
-
/**
|
|
56
|
+
/** 授权或改授权(同连接重复授权视为更新模式)。键在入口归一化,调用方传原始路径亦可 */
|
|
24
57
|
grant(projectKey, connId, mode) {
|
|
58
|
+
const key = normalizeProjectKey(projectKey);
|
|
25
59
|
const data = this.load();
|
|
26
|
-
const list = data.grants[
|
|
60
|
+
const list = data.grants[key] ?? [];
|
|
27
61
|
const existing = list.find((g) => g.connId === connId);
|
|
28
62
|
if (existing) {
|
|
29
63
|
existing.mode = mode;
|
|
@@ -32,27 +66,29 @@ export class GrantStore {
|
|
|
32
66
|
else {
|
|
33
67
|
list.push({ connId, mode, grantedAt: new Date().toISOString() });
|
|
34
68
|
}
|
|
35
|
-
data.grants[
|
|
69
|
+
data.grants[key] = list;
|
|
36
70
|
this.save(data);
|
|
37
71
|
}
|
|
38
72
|
/** 撤销某项目对某连接的授权。不存在时静默返回 */
|
|
39
73
|
revoke(projectKey, connId) {
|
|
74
|
+
const key = normalizeProjectKey(projectKey);
|
|
40
75
|
const data = this.load();
|
|
41
|
-
const list = data.grants[
|
|
76
|
+
const list = data.grants[key];
|
|
42
77
|
if (!list)
|
|
43
78
|
return;
|
|
44
79
|
const next = list.filter((g) => g.connId !== connId);
|
|
45
80
|
if (next.length === list.length)
|
|
46
81
|
return;
|
|
47
82
|
if (next.length === 0)
|
|
48
|
-
delete data.grants[
|
|
83
|
+
delete data.grants[key];
|
|
49
84
|
else
|
|
50
|
-
data.grants[
|
|
85
|
+
data.grants[key] = next;
|
|
51
86
|
this.save(data);
|
|
52
87
|
}
|
|
53
|
-
/** 检查授权模式;未授权返回 undefined(ro < rw,rw 覆盖 ro
|
|
88
|
+
/** 检查授权模式;未授权返回 undefined(ro < rw,rw 覆盖 ro)。键在入口归一化 */
|
|
54
89
|
check(projectKey, connId) {
|
|
55
|
-
const
|
|
90
|
+
const key = normalizeProjectKey(projectKey);
|
|
91
|
+
const modes = (this.load().grants[key] ?? [])
|
|
56
92
|
.filter((g) => g.connId === connId)
|
|
57
93
|
.map((g) => g.mode);
|
|
58
94
|
if (modes.includes('rw'))
|
package/dist/store/normalize.js
CHANGED
|
@@ -10,12 +10,21 @@
|
|
|
10
10
|
* 1. path.resolve → 转为绝对路径(相对路径基于 cwd 解析)
|
|
11
11
|
* 2. 分隔符统一为 '/'
|
|
12
12
|
* 3. Windows 盘符小写('C:/' 与 'c:/' 是同一目录)
|
|
13
|
-
* 4.
|
|
13
|
+
* 4. Windows 全路径小写(NTFS 默认大小写不敏感:'E:/Code/App' 与
|
|
14
|
+
* 'e:/code/app' 是同一目录。修复前仅盘符小写,路径段大小写保留,
|
|
15
|
+
* 会话 cwd 大小写与授权时不一致(重启/不同入口的常见现象)即授权
|
|
16
|
+
* 错位 → 「项目未授权该连接」,数据浏览树展开后无库无表)
|
|
17
|
+
* 5. 去尾分隔符('c:/code/proj/' → 'c:/code/proj')
|
|
18
|
+
*
|
|
19
|
+
* platform 参数仅供测试注入(默认 process.platform);非 Windows 文件
|
|
20
|
+
* 系统大小写敏感,路径段保持原样。
|
|
14
21
|
*/
|
|
15
22
|
import * as path from 'node:path';
|
|
16
|
-
export function normalizeProjectKey(p) {
|
|
23
|
+
export function normalizeProjectKey(p, platform = process.platform) {
|
|
17
24
|
let key = path.resolve(p).replace(/\\/g, '/');
|
|
18
25
|
key = key.replace(/^([A-Z]):/, (_m, drive) => `${drive.toLowerCase()}:`);
|
|
26
|
+
if (platform === 'win32')
|
|
27
|
+
key = key.toLowerCase();
|
|
19
28
|
if (key.length > 1)
|
|
20
29
|
key = key.replace(/\/+$/, '');
|
|
21
30
|
return key;
|
package/package.json
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-db-tool",
|
|
3
|
-
"version": "1.8.
|
|
3
|
+
"version": "1.8.8",
|
|
4
4
|
"description": "DSH community plugin: chat-operated multi-database admin tool with sidebar management, project-scoped grants, ro/rw modes and dangerous-operation confirmation",
|
|
5
5
|
"type": "module",
|
|
6
|
-
"license": "
|
|
6
|
+
"license": "Apache-2.0",
|
|
7
|
+
"author": "mengqi1436",
|
|
7
8
|
"repository": {
|
|
8
9
|
"type": "git",
|
|
9
10
|
"url": "git+https://github.com/mengqi1436/dsh-db-tool.git"
|
|
@@ -13,8 +14,8 @@
|
|
|
13
14
|
},
|
|
14
15
|
"homepage": "https://github.com/mengqi1436/dsh-db-tool#readme",
|
|
15
16
|
"engines": {
|
|
16
|
-
"node": ">=
|
|
17
|
-
"dsh": ">=0.1.0-rc.6 <0.2.0-0 || 0.1.7-rc.2 || >=0.2.0-rc.1 <0.3.0-0"
|
|
17
|
+
"node": ">=22",
|
|
18
|
+
"dsh": ">=0.1.0-rc.6 <0.2.0-0 || 0.1.7-rc.2 || >=0.2.0-rc.1 <0.3.0-0 || >=0.2.1-rc.2 <0.3.0-0"
|
|
18
19
|
},
|
|
19
20
|
"main": "dist/index.js",
|
|
20
21
|
"exports": {
|
|
@@ -74,6 +75,7 @@
|
|
|
74
75
|
"esbuild": "^0.28.0",
|
|
75
76
|
"mongodb": "^7.6.0",
|
|
76
77
|
"mssql": "12.7.4",
|
|
78
|
+
"semver": "^7.7.0",
|
|
77
79
|
"sql-formatter": "^15.9.0",
|
|
78
80
|
"stryker-cli": "^1.1.0",
|
|
79
81
|
"typescript": "^5.9.3",
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# DSH dsh-app-boot resolve.paths hotfix (upstream bug: createRequire(parent).resolve.paths(name)
|
|
2
|
+
# returns null for core-module names like "punycode", breaking hoisted profile installs).
|
|
3
|
+
# Covers BOTH unguarded for..of sites in 0.2.1-rc.3 lib/index.js (extracted from the
|
|
4
|
+
# shipped asar; CR=0, 4566 lines):
|
|
5
|
+
# nativePackageDir() for-line at 1371 -> hunk @@ -1368,7 +1368,10 @@
|
|
6
|
+
# routeScoped() for-line at 1628 -> hunk @@ -1625,7 +1628,14 @@
|
|
7
|
+
# Anchor lines are byte-identical to the 0.2.0-rc.2 patch (whole-file line shift only);
|
|
8
|
+
# hunk bodies are shared verbatim with 0.2.0-rc.1/rc.2.
|
|
9
|
+
# Target-Version: 0.2.1-rc.3
|
|
10
|
+
# Apply manually: git apply -p1 -d <dsh-app-boot package root> <this file>
|
|
11
|
+
# Or one command: npm run patch:dsh (scripts/apply-dsh-hotfix.ps1)
|
|
12
|
+
# Generated from @deepseek-ai/dsh-app-boot@0.2.1-rc.3 (asar) vs patched local copy.
|
|
13
|
+
diff --git a/lib/index.js b/lib/index.js
|
|
14
|
+
index 39401ea..8c053c3 100644
|
|
15
|
+
--- a/lib/index.js
|
|
16
|
+
+++ b/lib/index.js
|
|
17
|
+
@@ -1368,7 +1368,10 @@ function profileChild(path, treePrefix) {
|
|
18
|
+
return treePrefix + (end < 0 ? rest : rest.slice(0, end));
|
|
19
|
+
}
|
|
20
|
+
function nativePackageDir(parent, name) {
|
|
21
|
+
- for (const searchPath of createRequire(parent).resolve.paths(name)) {
|
|
22
|
+
+ /* local hotfix: nativePackageDir: resolve.paths returns null for core-module names
|
|
23
|
+
+ (fs, node:fs, ...); an empty list means no local candidate, matching the
|
|
24
|
+
+ guard in profile.ts:596. */
|
|
25
|
+
+ for (const searchPath of createRequire(parent).resolve.paths(name) ?? []) {
|
|
26
|
+
const candidate = join(searchPath, name);
|
|
27
|
+
if (existsSync(join(candidate, "package.json"))) return candidate;
|
|
28
|
+
}
|
|
29
|
+
@@ -1625,7 +1628,14 @@ var ResolutionRouter = class {
|
|
30
|
+
const target = resolution.entries.get(name);
|
|
31
|
+
const candidates = [];
|
|
32
|
+
const localSearchPaths = [];
|
|
33
|
+
- for (const searchPath of createRequire(parent).resolve.paths(name)) {
|
|
34
|
+
+ /* local hotfix: resolve.paths returns null for core-module names (e.g. "punycode"),
|
|
35
|
+
+ which made the for..of throw TypeError and broke every hoisted profile install
|
|
36
|
+
+ whose dependency tree requires such an npm package. Fall back to the standard
|
|
37
|
+
+ node_modules search chain so the local candidate can still be found. */
|
|
38
|
+
+ const _hotReq = createRequire(parent);
|
|
39
|
+
+ const _hotPaths = _hotReq.resolve.paths(name)
|
|
40
|
+
+ ?? _hotReq("node:module")._nodeModulePaths(_hotReq("node:path").dirname(parent));
|
|
41
|
+
+ for (const searchPath of _hotPaths) {
|
|
42
|
+
if (!searchPath.startsWith(layer.localPrefix)) break;
|
|
43
|
+
localSearchPaths.push(searchPath);
|
|
44
|
+
const candidate = localPackageCandidate(searchPath, name, flavor);
|
package/scripts/hotfix-core.mjs
CHANGED
|
@@ -23,13 +23,15 @@ import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
|
23
23
|
|
|
24
24
|
/** 补丁文件里的目标版本声明行(patches/*.patch 注释头)。 */
|
|
25
25
|
export const TARGET_VERSION = '0.1.7-rc.2';
|
|
26
|
-
/** 锚点行逐字节一致的受支持版本列表(仅整体行号偏移;0.2.0-rc.1/0.2.0-rc.2 的 lib/index.js
|
|
27
|
-
|
|
26
|
+
/** 锚点行逐字节一致的受支持版本列表(仅整体行号偏移;0.2.0-rc.1/0.2.0-rc.2 的 lib/index.js 逐字节相同;
|
|
27
|
+
* 0.2.1-rc.3 锚点内容同样逐字节一致,仅整体行号偏移(npd +123 / routeScoped +205)。 */
|
|
28
|
+
export const TARGET_VERSIONS = ['0.1.7-rc.2', '0.2.0-rc.1', '0.2.0-rc.2', '0.2.1-rc.3'];
|
|
28
29
|
/** 各受支持版本对应的默认补丁文件名(patches/ 下;git-apply 路径按版本选择)。 */
|
|
29
30
|
export const PATCH_BY_VERSION = {
|
|
30
31
|
'0.1.7-rc.2': 'dsh-app-boot-route-scoped-hotfix.patch',
|
|
31
32
|
'0.2.0-rc.1': 'dsh-app-boot-route-scoped-hotfix-0.2.0-rc.1.patch',
|
|
32
33
|
'0.2.0-rc.2': 'dsh-app-boot-route-scoped-hotfix-0.2.0-rc.2.patch',
|
|
34
|
+
'0.2.1-rc.3': 'dsh-app-boot-route-scoped-hotfix-0.2.1-rc.3.patch',
|
|
33
35
|
};
|
|
34
36
|
/** 判断目标文件是否已打补丁的标识串(hotfix 注释首行片段)。 */
|
|
35
37
|
export const HOTFIX_MARKER = 'local hotfix: resolve.paths returns null';
|