dsh-db-tool 1.3.2 → 1.4.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.
package/README.md CHANGED
@@ -1,11 +1,13 @@
1
1
  # dsh-db-tool
2
2
 
3
+ [English](README_EN.md) | 简体中文
4
+
3
5
  DSH 社区插件:在聊天中安全操作数据库,配套侧边栏管理台与 `db-admin` skill。架构与交互模式对齐 [dsh-ssh-tunnel](https://github.com/thirsty5034/dsh-ssh-tunnel)。
4
6
 
5
7
  ## 功能
6
8
 
7
- - **8 种数据库**:MySQL、PostgreSQL、GaussDB(openGauss 官方驱动)、SQLite、Redis、MongoDB、Oracle、达梦(DM)
8
- - **DatabaseManager 单工具多 action**:`list_connections / query / execute / schema / preview / run_script`
9
+ - **8 种数据库**:MySQL、PostgreSQL、GaussDB、SQLite、Redis、MongoDB、Oracle、达梦(DM)
10
+ - **DatabaseManager 单工具多 action**:`list_connections / query / execute / schema / preview / run_script`(`run_script` 在 node:vm 沙箱中执行,60s 超时,仅注入受限 `db.{query,execute}` 句柄)
9
11
  - **分级权限**:连接级只读(ro)/读写(rw)+ 项目级授权(`grants.json`:projectPathKey → 连接 → 模式);未授权项目一律拒绝
10
12
  - **危险操作确认**:DDL / FLUSHALL / dropDatabase 等先返回 `NEEDS_CONFIRMATION`,对话内(模型经 ask)或 SQL 控制台(弹窗)确认后携一次性 `challengeId`(绑定语句 SHA256、5 分钟过期)重试
11
13
  - **审计**:全部执行落 `audit.jsonl`(语句、危险级、是否确认、结果)
@@ -25,6 +27,10 @@ DSH 社区插件:在聊天中安全操作数据库,配套侧边栏管理台
25
27
 
26
28
  ## 安装
27
29
 
30
+ 8 种数据库驱动全部随 npm 包分发(SQLite 优先用 Node 内置 `node:sqlite`,可选 `better-sqlite3`;GaussDB 驱动为华为云官方 npm 包 `gaussdb-node`),无需任何构建步骤。GaussDB 认证支持 sha256 与 md5,md5-sha256 混合与 SM3 暂不支持。
31
+
32
+ 唯一的一次性交互来自 oracledb:其 install 脚本会被 pnpm 默认拦截并使首次安装报告失败——在 DSH 插件安装界面点 **"Allow these scripts and retry"** 即可完成安装(该脚本仅做 Node 版本检查与横幅打印,批准无风险;批准持久化到 profile,后续升级不再提示)。
33
+
28
34
  ```bash
29
35
  # npm(推荐)
30
36
  dsh plugin --profile web add dsh-db-tool
@@ -36,8 +42,6 @@ dsh plugin --profile web add "dsh-db-tool@github:mengqi1436/dsh-db-tool"
36
42
  dsh plugin --profile web add "link:E:\path\to\dsh-db-tool"
37
43
  ```
38
44
 
39
- GaussDB 官方驱动未发布 npm,需先构建 vendor:`npm run build:gaussdb`(PowerShell)或 `bash scripts/build-gaussdb.sh`;运行要求 Node ≥ 22.12(产物依赖 p-limit@7 纯 ESM)。oracledb 安装脚本需 `npm approve-scripts oracledb`。mongodb 驱动已 bundle 进发布包(`vendor/mongodb-driver.cjs`,esbuild 构建,`npm run build:mongodb` 可重建),依赖树中不含 mongodb/punycode(守卫见「Troubleshooting」免补丁路径说明)。
40
-
41
45
  ### 桌面端安装
42
46
 
43
47
  DSH 桌面端 0.2.0-rc.2+ 捆绑了 `dsh` 命令,全程无需另装 Node 或 pnpm:
@@ -46,64 +50,13 @@ DSH 桌面端 0.2.0-rc.2+ 捆绑了 `dsh` 命令,全程无需另装 Node 或 p
46
50
  2. **安装插件**(必须钉精确版本:`@latest` 会因 release-age 校验回落到旧版):
47
51
 
48
52
  ```bash
49
- dsh plugin --profile desktop add dsh-db-tool@1.3.1 --registry=https://registry.npmjs.org/
53
+ dsh plugin --profile desktop add dsh-db-tool@1.4.0 --registry=https://registry.npmjs.org/
50
54
  ```
51
55
 
52
56
  已有 npm 镜像源偏好的用户,可把 `--registry` 替换为自己的镜像地址。
53
57
  3. **重启桌面端**生效。
54
58
 
55
- 桌面端补充说明:
56
-
57
- - **免补丁路径**:桌面端宿主代码打包在 `app.asar` 内,无法应用 `patch:dsh` 热补丁;但本插件 0.1.9+ 走免补丁路径(mongodb 驱动已 bundle 为 `vendor/mongodb-driver.cjs` 随包发布,依赖树不含 punycode),桌面端可直接安装加载,无需任何补丁。
58
- - **GaussDB**:npm 发布包不含 GaussDB vendor 驱动。需要 GaussDB 的用户请改从源码安装(`dsh plugin --profile desktop add "dsh-db-tool@github:mengqi1436/dsh-db-tool"`),并在插件目录自行 `npm run build:gaussdb`(要求 Node ≥ 22.12)。
59
- - **Oracle**:安装后需 `npm approve-scripts oracledb` 放行 oracledb 安装脚本(或按 DSH 插件安装界面的脚本审批提示放行)。
60
-
61
- ### 离线环境安装
62
-
63
- **症状(≤ 1.3.1)**:受限代理/无公网 + 无编译工具链的机器上,安装卡死在 `better-sqlite3`(原生 C++ 模块)后整体回滚(`ERR_PNPM_EXECOR_LIFECYCLE_SCRIPT_FAILED`)——官方未发布 Node 24(ABI v137)的 Windows 预构建(release 下载 404),本地编译又同时缺 Node 头文件(nodejs.org 经代理 TLS 异常)与 MSVC 工具链。
64
-
65
- **1.3.2 起的行为**:
66
-
67
- 1. `better-sqlite3` 移入 `optionalDependencies`——构建失败时 pnpm 自动排除该包,**插件整体安装不再回滚**(pnpm 11.7 实测:`is an optional dependency and failed … Excluding it from installation`,exit 0)。
68
- 2. SQLite 改走**双驱动**:优先 `node:sqlite`(Node ≥ 22.5 内置,零原生依赖、离线环境直接可用;DSH 桌面端 runtime Node 24.x 自带),降级 `better-sqlite3`(存在则自动使用)。两者皆无时给出明确指引,**其余 7 种数据库不受任何影响**(MySQL/PG/GaussDB/Redis/Mongo/Oracle/DM 均为纯 JS 驱动)。
69
- 3. 可用环境变量 `DBT_SQLITE_DRIVER=node|better` 强制单一路径(默认自动选择)。
70
-
71
- SQLite 支持矩阵:
72
-
73
- | 环境 | SQLite 可用性 |
74
- |---|---|
75
- | Node ≥ 22.5(含 DSH 桌面端 runtime 24.x) | ✅ `node:sqlite` 内置,零依赖 |
76
- | Node < 22.5 且 better-sqlite3 可安装/编译 | ✅ 自动用 `better-sqlite3` |
77
- | Node < 22.5 且离线(better-sqlite3 构建失败被排除) | ❌ SQLite 不可用,其余 7 库正常 |
78
-
79
- 老版本(≤ 1.3.1)离线机的应急路径:有外网窗口时重试、装 VS Build Tools 后 `pnpm rebuild better-sqlite3`、或直接升级到 ≥ 1.3.2(推荐)。
80
-
81
- ## 安全模型
82
-
83
- - HTTP API 仅同源(`ctx.webServer` prefix `/dsh-db-tool/api`)+ 宿主 Connection 栅栏(`ctx.connection.requestRejection`:Host/Origin + 浏览器 token 鉴权。0.2.0-rc.1 起插件前缀路由不经宿主栅栏,故在 handler 顶层显式应用;宿主无 connection 服务时回退 loopback/Origin trust 校验,等价 ssh-tunnel `http-trust`),body 限 2MB
84
- - ro 双保险:服务层拦截 + 驱动会话级 `SET SESSION CHARACTERISTICS AS TRANSACTION READ ONLY`(pg/gaussdb)、readonly 打开(SQLite)
85
- - SQL 参数绑定 + 标识符白名单(`[A-Za-z0-9_$]+` + 引用包裹);MySQL `multipleStatements:false`;Redis 元数据走 `SCAN`(禁 `KEYS`);Mongo 递归拒 `$where`
86
- - `run_script`:node:vm 独立 context、60s 超时、无 require/process/网络/文件系统,仅注入受限 `db.{query,execute}` 句柄
87
- - 已知边界:对话内确认为提示级强制 + 审计兜底;DSH 无硬中断通道前,恶意对话仍可能诱导用户确认,请配合最小权限数据库账号使用
88
-
89
- ## Troubleshooting
90
-
91
- ### npm 安装插件导入失败(punycode / resolve.paths)
92
-
93
- 症状:DSH 启动后插件加载报 `failed to import`,伴随 `TypeError: Cannot read properties of null (reading 'Symbol(Symbol.iterator)')`,栈指向 `dsh-app-boot` 的 `routeScoped`。
94
-
95
- 根因:上游 `@deepseek-ai/dsh-app-boot` 对 `createRequire(parent).resolve.paths(name)` 直接做 `for..of`,而 Node 对 core-module 同名包(`punycode` 等)返回 `null`,hoisted profile 下凡依赖树含此类 npm 包的插件都会炸。`0.1.7-rc.2`、`0.2.0-rc.1` 与 `0.2.0-rc.2` 中同型缺陷两处(`0.2.0-rc.2` 的 `lib/index.js` 与 `0.2.0-rc.1` 逐字节相同,尚未修复;锚点行均与 `0.1.7-rc.2` 一致,仅整体行号偏移 1 行):`routeScoped()` 与 `nativePackageDir()`,本补丁一并修复(两处分别独立判定,已打过旧版单处补丁的机器只会补上缺的那处)。
96
-
97
- 一键修复(幂等,应用前自动备份为 `index.js.bak-hotfix`;`--revert` 可还原):
98
-
99
- ```bash
100
- npm run patch:dsh # Windows(PowerShell)
101
- npm run patch:dsh:sh # macOS / Linux
102
- ```
103
-
104
- 脚本自动探测 DSH 安装根(`--dsh-root` 可显式指定);补丁文件按安装版本自动选择:`patches/dsh-app-boot-route-scoped-hotfix.patch`(`0.1.7-rc.2`)、`patches/dsh-app-boot-route-scoped-hotfix-0.2.0-rc.1.patch`(`0.2.0-rc.1`)、`patches/dsh-app-boot-route-scoped-hotfix-0.2.0-rc.2.patch`(`0.2.0-rc.2`,其 `lib/index.js` 与 `0.2.0-rc.1` 逐字节相同,故 hunk 完全一致;均与内建替换路径逐字节等价已验证);不支持的版本会警告(`--force` 覆盖)。上游 issue:<https://github.com/mengqi1436/dsh-db-tool/issues>(占位,待上游仓库开放后替换)。
105
-
106
- **本插件的免补丁路径(0.1.9+)**:该 bug 的触发条件是依赖树中出现 core-module 同名包,mongodb 链(`mongodb-connection-string-url → whatwg-url → tr46 → punycode`)正是元凶。0.1.9 起 mongodb 驱动经 esbuild bundle 为 `vendor/mongodb-driver.cjs` 随包发布,生产依赖树不再含 mongodb 与 punycode——在**未修复宿主**(含桌面版 app.asar,无法打补丁)上也能正常安装加载。守卫测试 `tests/guard/deps-core-collision.spec.ts` 断言依赖树永不回退(dependencies 白名单 + lockfile 生产树 core 同名包零交集)。`patch:dsh` 仍保留,用于修复**其他**含同类依赖的插件或宿主自身报错。
59
+ mongodb 驱动已 bundle 进发布包(`vendor/mongodb-driver.cjs`),依赖树不含 mongodb/punycode——桌面端宿主打包在 `app.asar` 内、无法应用宿主补丁,本插件免补丁可直接安装加载。
107
60
 
108
61
  ## 测试
109
62
 
@@ -115,14 +68,19 @@ npx stryker run # 变异测试(范围 lib/guard + lib/manager + lib/store,
115
68
 
116
69
  真机冒烟(设了才跑):`DBT_TEST_MYSQL_URL / DBT_TEST_PG_URL / DBT_TEST_REDIS_URL / DBT_TEST_DM_CONNECT / DBT_TEST_MONGO_URL / DBT_TEST_ORACLE_CONNECT`。GaussDB 与 Oracle/Mongo 官方要求均按官方文档实现,未真机验证处以代码内标注为准。
117
70
 
118
- ## 目录
71
+ ## 目录结构
119
72
 
120
73
  ```
121
74
  lib/ host 插件(store / adapters×8 / guard / manager / http / index)
122
75
  client/ 侧边栏单文件产物(client.js,即源码)
123
76
  skills/ db-admin skill
124
- scripts/ GaussDB vendor 构建、mongodb 驱动 bundle(build:mongodb)、DSH dsh-app-boot 热修复(patch:dsh)
125
- docs/ 安装、HTTP 契约(api-contract.md)、skill 说明
77
+ scripts/ 构建与工具脚本(mongodb 驱动 bundle、DSH 宿主热修复 patch:dsh 等)
78
+ patches/ DSH 宿主 dsh-app-boot 热修复补丁(patch:dsh 按宿主版本选用)
79
+ docs/ 预留(当前为空)
126
80
  tests/ vitest(离线 mock + DBT_TEST_* 门控真机)
127
- vendor/ gaussdb 构建源/产物与 mongodb-driver.cjs bundle(gitignore,发布经 files 白名单收录 bundle)
81
+ vendor/ mongodb-driver.cjs bundle(gitignore,发布经 files 白名单收录)
128
82
  ```
83
+
84
+ ## 许可证
85
+
86
+ MIT
package/README_EN.md ADDED
@@ -0,0 +1,86 @@
1
+ # dsh-db-tool
2
+
3
+ [简体中文](README.md) | English
4
+
5
+ A DSH community plugin for operating databases safely from chat, with a sidebar management console and the `db-admin` skill. Architecture and interaction patterns follow [dsh-ssh-tunnel](https://github.com/thirsty5034/dsh-ssh-tunnel).
6
+
7
+ ## Features
8
+
9
+ - **8 databases**: MySQL, PostgreSQL, GaussDB, SQLite, Redis, MongoDB, Oracle, and DM (Dameng)
10
+ - **One tool, six actions**: `DatabaseManager` exposes `list_connections / query / execute / schema / preview / run_script` (`run_script` executes in a `node:vm` sandbox with a 60s timeout and only restricted `db.{query,execute}` handles)
11
+ - **Tiered permissions**: per-connection read-only (ro) / read-write (rw) plus project-scoped grants (`grants.json`: projectPathKey → connection → mode); unauthorized projects are always rejected
12
+ - **Dangerous-operation confirmation**: DDL / FLUSHALL / dropDatabase and the like first return `NEEDS_CONFIRMATION`; after confirmation in chat (model via ask) or in the SQL console (dialog), the retry carries a one-time `challengeId` (bound to the statement SHA256, 5-minute TTL)
13
+ - **Auditing**: every execution is appended to `audit.jsonl` (statement, danger level, confirmed or not, outcome)
14
+ - **Sidebar with 4 panels** (dsh-better-sidebar, zh/en): connections, project grants, data browser, SQL console
15
+ - **db-admin skill**: shipped with the plugin, covering dialect cheat sheets for all 8 databases, safety rules, and the confirmation flow
16
+
17
+ ## Data Layout
18
+
19
+ `$DSH_HOME/db-tool/` (0700):
20
+
21
+ | File | Contents |
22
+ |---|---|
23
+ | `connections.json` | connection definitions (passwords masked as `***` in `urlSafe`) |
24
+ | `secrets.json` | 0600; password/URL credentials, never returned to the model |
25
+ | `grants.json` | project grants (normalized path → connId → ro/rw) |
26
+ | `audit.jsonl` | append-only audit log |
27
+
28
+ ## Installation
29
+
30
+ Drivers for all 8 databases ship with the npm package (SQLite prefers the built-in `node:sqlite`, optionally `better-sqlite3`; the GaussDB driver is the official Huawei Cloud npm package `gaussdb-node`) — no build steps of any kind. GaussDB authentication supports sha256 and md5; md5-sha256 hybrid and SM3 are not supported yet.
31
+
32
+ The only one-time interaction comes from oracledb: pnpm blocks its install script by default, which makes the first install report a failure — click **"Allow these scripts and retry"** in the DSH plugin installation UI to finish (the script only checks the Node version and prints a banner, so approving is risk-free; the approval is persisted to the profile and upgrades won't prompt again).
33
+
34
+ ```bash
35
+ # npm (recommended)
36
+ dsh plugin --profile web add dsh-db-tool
37
+
38
+ # From GitHub source
39
+ dsh plugin --profile web add "dsh-db-tool@github:mengqi1436/dsh-db-tool"
40
+
41
+ # Local development
42
+ dsh plugin --profile web add "link:E:\path\to\dsh-db-tool"
43
+ ```
44
+
45
+ ### Desktop installation
46
+
47
+ DSH Desktop 0.2.0-rc.2+ bundles the `dsh` command — no separate Node or pnpm required:
48
+
49
+ 1. **First use**: open the desktop menu bar, click **"Manage dsh command"**, and install the bundled CLI, which registers `dsh` on the system PATH.
50
+ 2. **Install the plugin** (always pin an exact version: `@latest` falls back to an older release due to release-age checks):
51
+
52
+ ```bash
53
+ dsh plugin --profile desktop add dsh-db-tool@1.4.0 --registry=https://registry.npmjs.org/
54
+ ```
55
+
56
+ If you prefer an npm mirror, replace `--registry` with your mirror URL.
57
+ 3. **Restart** the desktop app.
58
+
59
+ The MongoDB driver is bundled into the published package (`vendor/mongodb-driver.cjs`), so the dependency tree contains no mongodb/punycode — the desktop host is packed inside `app.asar` and cannot be host-patched, yet this plugin installs and loads there without any patch.
60
+
61
+ ## Testing
62
+
63
+ ```bash
64
+ npm test # full offline mock suite (incl. e2e-mock end-to-end and adversarial cases)
65
+ npx tsc --noEmit
66
+ npx stryker run # mutation testing (lib/guard + lib/manager + lib/store, reports in reports/mutation/)
67
+ ```
68
+
69
+ Live-database smoke tests (run only when set): `DBT_TEST_MYSQL_URL / DBT_TEST_PG_URL / DBT_TEST_REDIS_URL / DBT_TEST_DM_CONNECT / DBT_TEST_MONGO_URL / DBT_TEST_ORACLE_CONNECT`. GaussDB, Oracle, and Mongo official requirements are implemented per their official docs; anything not verified against live databases is annotated in code.
70
+
71
+ ## Project Structure
72
+
73
+ ```
74
+ lib/ host plugin (store / adapters×8 / guard / manager / http / index)
75
+ client/ sidebar single-file artifact (client.js — this is the source)
76
+ skills/ db-admin skill
77
+ scripts/ build & tooling scripts (MongoDB driver bundle, DSH host hotfix patch:dsh, etc.)
78
+ patches/ DSH host dsh-app-boot hotfix patches (selected by host version for patch:dsh)
79
+ docs/ reserved (currently empty)
80
+ tests/ vitest (offline mocks + DBT_TEST_*-gated live runs)
81
+ vendor/ mongodb-driver.cjs bundle (gitignored; shipped via the files whitelist)
82
+ ```
83
+
84
+ ## License
85
+
86
+ MIT
package/client/client.js CHANGED
@@ -34,6 +34,8 @@ window.__ModuleLoader__.load({
34
34
  urlMode: "URL 方式",
35
35
  fieldsMode: "分字段方式",
36
36
  url: "连接 URL(密码将自动拆入密钥存储)",
37
+ urlUser: "用户名(可选,自动注入 URL)",
38
+ urlPassword: "密码(可选,自动注入 URL;留空保持已存)",
37
39
  fieldsHost: "主机地址",
38
40
  fieldsPort: "端口",
39
41
  fieldsUser: "用户名",
@@ -123,6 +125,8 @@ window.__ModuleLoader__.load({
123
125
  urlMode: "URL",
124
126
  fieldsMode: "Fields",
125
127
  url: "Connection URL (password moved to secrets automatically)",
128
+ urlUser: "Username (optional, injected into URL)",
129
+ urlPassword: "Password (optional, injected into URL; blank keeps saved)",
126
130
  fieldsHost: "Host",
127
131
  fieldsPort: "Port",
128
132
  fieldsUser: "User",
@@ -375,7 +379,7 @@ window.__ModuleLoader__.load({
375
379
  }
376
380
 
377
381
  /* ---------------- 连接管理 ---------------- */
378
- const EMPTY_FORM = { id: "", kind: "mysql", name: "", mode: "url", url: "", host: "", port: "", user: "", password: "", database: "", ssl: false };
382
+ const EMPTY_FORM = { id: "", kind: "mysql", name: "", mode: "url", url: "", urlUser: "", urlPassword: "", host: "", port: "", user: "", password: "", database: "", ssl: false };
379
383
 
380
384
  // 通用表格渲染:columns + rows(审计列表与查询结果共用)
381
385
  function resultTable(columns, rows, cellTitles) {
@@ -457,6 +461,7 @@ window.__ModuleLoader__.load({
457
461
  ...EMPTY_FORM,
458
462
  id: c.id, kind: c.kind, name: c.name || "", mode,
459
463
  url: c.safeUrl || "",
464
+ urlUser: "", urlPassword: "",
460
465
  host: c.host || "", port: c.port != null ? String(c.port) : "",
461
466
  user: c.user || "", database: c.database || "",
462
467
  ssl: !!c.ssl,
@@ -496,6 +501,11 @@ window.__ModuleLoader__.load({
496
501
  if (form.url && form.url !== origUrl) body.url = form.url;
497
502
  // 新建 url 连接必须发
498
503
  if (!props.initial && form.url) body.url = form.url;
504
+ // URL 模式独立凭据:trim 后非空才发(服务端注入 userinfo;url 未发时以已存 URL 为基底)
505
+ const uu = (form.urlUser || "").trim();
506
+ const up = (form.urlPassword || "").trim();
507
+ if (uu) body.urlUser = uu;
508
+ if (up) body.urlPassword = up;
499
509
  } else {
500
510
  body.fields = { host: form.host, user: form.user, database: form.database || undefined };
501
511
  if (form.port) body.fields.port = Number(form.port);
@@ -541,7 +551,14 @@ window.__ModuleLoader__.load({
541
551
  React.createElement("button", { className: form.mode === "fields" ? "active" : "", onClick: reenterFields }, t("fieldsMode")),
542
552
  ),
543
553
  form.mode === "url"
544
- ? React.createElement("input", { placeholder: t("url"), value: form.url, onChange: (e) => patch({ url: e.target.value }) })
554
+ ? React.createElement(
555
+ "div",
556
+ { style: { display: "flex", flexDirection: "column", gap: 6 } },
557
+ React.createElement("input", { placeholder: t("url"), value: form.url, onChange: (e) => patch({ url: e.target.value }) }),
558
+ React.createElement("div", { className: "dbt-row" },
559
+ React.createElement("input", { placeholder: t("urlUser"), value: form.urlUser, onChange: (e) => patch({ urlUser: e.target.value }) }),
560
+ React.createElement("input", { type: "password", placeholder: t("urlPassword"), value: form.urlPassword, onChange: (e) => patch({ urlPassword: e.target.value }) })),
561
+ )
545
562
  : React.createElement(
546
563
  "div",
547
564
  { style: { display: "flex", flexDirection: "column", gap: 6 } },
@@ -1,14 +1,21 @@
1
1
  /**
2
2
  * GaussDB(openGauss)适配器。
3
3
  *
4
- * ⚠️ 官方驱动 openGauss-connector-nodejs 未发布 npm(华为云 HCS 26.861.0 官方文档
5
- * 应用程序接口清单仅 JDBC/ODBC/libpq/Psycopg/Go/ECPG,无 Node 驱动),Node 接入基于
6
- * PostgreSQL 协议兼容自行验证。使用 vendor/gaussdb-pg 构建产物(fork 自 pg 8.6.0,
7
- * API 与 pg 完全兼容),未真机验证。
4
+ * 驱动:npm 包 gaussdb-node(华为云官方组织 HuaweiCloudDeveloper/gaussdb-node,
5
+ * pg 兼容 fork,导出结构同 pg:Pool/Client)。npm 安装即含 GaussDB 支持,无需
6
+ * 额外构建;依赖树(gaussdb-connection-string/gaussdb-pool/gaussdb-protocol/
7
+ * pg-types/pgpass/gaussdb-cloudflare)无 p-limit 等高版本要求,Node ≥ 20 即可。
8
+ *
9
+ * 认证支持(依包内实现核对,未真机验证):
10
+ * - sha256(RFC5802,password_encryption_type=2 默认):已实现——
11
+ * gaussdb-node/lib/crypto/rfc5802.js + client.js _handleAuthSHA256Password、
12
+ * gaussdb-protocol parser AuthenticationSHA256Password(case 10)分支;
13
+ * - md5(标准 PG 形态 md5(md5(password+user)+salt)):已实现(gaussdbMd5PasswordHash);
14
+ * - md5-sha256 混合(type=1)与 SM3(type=3):包内未提供(rfc5802 仅支持 sha256
15
+ * method、无 gm-crypto 依赖)——sha256/md5 已证,md5-sha256 混合与 SM3 未证,
16
+ * 服务端要求这两种认证方式时会握手失败。
8
17
  *
9
18
  * 已按官方文档对齐(证据见 skills/db-admin/SKILL.md GaussDB 节):
10
- * - 认证:驱动实现 openGauss SHA256 / MD5-SHA256 / SM3 握手(vendor pg-protocol parser.ts:335-357、
11
- * rfc5802.js:44),对应服务端 password_encryption_type 默认 2 (sha256);
12
19
  * - 默认端口 8000(集中式 DN / 分布式 CN)、ro 会话 `SET SESSION CHARACTERISTICS AS
13
20
  * TRANSACTION READ ONLY` 两形态官方支持(rf-dist/gaussdb-08-0400、rf-cent/gaussdb-38-0416);
14
21
  * - 已知限制:SERIALIZABLE 功能不支持(等价 REPEATABLE READ,与 PG 的 SSI 行为不同);
@@ -16,25 +23,20 @@
16
23
  * information_schema.tables/columns 集中式有官方视图文档、分布式参考无原生视图文档
17
24
  * (仅 M-Compatibility 模式),跨库浏览在分布式实例上以真机行为为准。
18
25
  *
19
- * 构建方式:bash scripts/build-gaussdb.sh(或 npm run build:gaussdb)
20
26
  * 核心逻辑与 postgresql 适配器共享(sql-shared/pg-like.ts)。
21
27
  */
22
28
  import { createRequire } from 'node:module';
23
29
  import { createPgLikeAdapter, } from '../sql-shared/pg-like.js';
24
30
  const require = createRequire(import.meta.url);
25
- // packages 布局:pg lib 内 require('../../pg-protocol'/'../../pg-pool') 按 monorepo 假设向上两级解析
26
- // 相对 createRequire base(lib/adapters/gaussdb/)需三级到项目根:gaussdb/ → adapters/ → lib/ → root
27
- const VENDOR_PATH = '../../../vendor/gaussdb-pg/packages/pg';
28
31
  function loadGaussDriver() {
29
32
  try {
30
- // vendor 为 CJS 产物,结构同 pg:module.exports = { Pool, Client, ... }
31
- return require(VENDOR_PATH);
33
+ // gaussdb-node 为 CJS 包,结构同 pg:module.exports = { Pool, Client, ... }
34
+ return require('gaussdb-node');
32
35
  }
33
36
  catch (e) {
34
- // 附原始错误:区分「vendor 未构建」与「构建产物损坏/依赖缺失」两类故障
37
+ // 附原始错误:区分「依赖未安装」与「安装产物损坏」两类故障
35
38
  const msg = e instanceof Error ? e.message : String(e);
36
- throw new Error('GaussDB 驱动未找到:官方 openGauss-connector-nodejs 未发布 npm,' +
37
- '请先执行 bash scripts/build-gaussdb.sh(或 npm run build:gaussdb)构建 vendor/gaussdb-pg。' +
39
+ throw new Error('GaussDB 驱动未找到:请重新安装插件(npm install gaussdb-node 或重装 dsh-db-tool)。' +
38
40
  `原始错误: ${msg}`);
39
41
  }
40
42
  }
@@ -12,7 +12,7 @@ const LOADERS = {
12
12
  const INSTALL_HINTS = {
13
13
  mysql: 'npm install mysql2',
14
14
  postgresql: 'npm install pg',
15
- gaussdb: 'npm run build:gaussdb(构建 vendor/gaussdb-pg)',
15
+ gaussdb: 'npm install gaussdb-node(或重装插件恢复依赖)',
16
16
  sqlite: '升级 Node ≥ 22.5(内置 node:sqlite),或 npm install better-sqlite3',
17
17
  redis: 'npm install redis',
18
18
  mongodb: 'npm run build:mongodb(构建 vendor/mongodb-driver.cjs;发布包已内置产物,仅在缺失时需要)',
@@ -166,7 +166,9 @@ export async function createMongoAdapter(conn, opts) {
166
166
  }
167
167
  const dbName = typeof conn.meta.database === 'string' && conn.meta.database !== ''
168
168
  ? conn.meta.database
169
- : defaultDbName(conn.fields);
169
+ : conn.url
170
+ ? urlDbName(conn.url)
171
+ : defaultDbName(conn.fields);
170
172
  const db = client.db(dbName);
171
173
  function wrap(e) {
172
174
  const M = vendorOrNull();
@@ -524,15 +526,30 @@ function inferBsonType(v) {
524
526
  return 'object';
525
527
  return typeof v;
526
528
  }
527
- function buildUri(fields, ssl) {
529
+ export function buildUri(fields, ssl) {
528
530
  const host = typeof fields?.host === 'string' && fields.host !== '' ? fields.host : '127.0.0.1';
529
531
  const port = typeof fields?.port === 'number' ? fields.port : 27017;
530
532
  const user = typeof fields?.user === 'string' ? encodeURIComponent(fields.user) : '';
531
533
  const pass = typeof fields?.password === 'string' ? `:${encodeURIComponent(fields.password)}` : '';
532
534
  const auth = user !== '' ? `${user}${pass}@` : '';
533
- const scheme = ssl || fields?.ssl === true || fields?.tls === true ? 'mongodb+srv' : 'mongodb';
534
- const authDb = typeof fields?.authDatabase === 'string' ? `?authSource=${fields.authDatabase}` : '';
535
- return `${scheme}://${auth}${host}:${port}${authDb}`;
535
+ // tls 用查询参数表达:mongodb+srv 是 DNS seedlist 形态(禁止显式端口),与 TLS 无关,不可混用
536
+ const params = [];
537
+ if (typeof fields?.authDatabase === 'string' && fields.authDatabase !== '') {
538
+ params.push(`authSource=${fields.authDatabase}`);
539
+ }
540
+ if (ssl || fields?.ssl === true || fields?.tls === true)
541
+ params.push('tls=true');
542
+ const qs = params.length > 0 ? `?${params.join('&')}` : '';
543
+ return `mongodb://${auth}${host}:${port}${qs}`;
544
+ }
545
+ /** URL 模式库名:取 URL path 首段(percent 解码),无则 'test' 兜底(与 pg-like mainDb 同口径) */
546
+ function urlDbName(url) {
547
+ try {
548
+ return decodeURIComponent(new URL(url).pathname.replace(/^\//, '')) || 'test';
549
+ }
550
+ catch {
551
+ return 'test';
552
+ }
536
553
  }
537
554
  function defaultDbName(fields) {
538
555
  return typeof fields?.database === 'string' && fields.database !== '' ? fields.database : 'test';
@@ -141,6 +141,8 @@ async function route(req, res, service, path, q, opts) {
141
141
  id, kind: kind,
142
142
  ...(b['name'] !== undefined ? { name: str(b['name']) } : {}),
143
143
  ...(b['url'] !== undefined ? { url: str(b['url']) } : {}),
144
+ ...(b['urlUser'] !== undefined ? { urlUser: str(b['urlUser']) } : {}),
145
+ ...(b['urlPassword'] !== undefined ? { urlPassword: str(b['urlPassword']) } : {}),
144
146
  ...(b['fields'] !== undefined ? { fields: b['fields'] } : {}),
145
147
  ...(ssl !== undefined ? { ssl } : {}),
146
148
  }));
@@ -158,6 +160,8 @@ async function route(req, res, service, path, q, opts) {
158
160
  return sendMaybeConfirm(res, service.testDraft({
159
161
  kind: kind,
160
162
  ...(b['url'] !== undefined ? { url: str(b['url']) } : {}),
163
+ ...(b['urlUser'] !== undefined ? { urlUser: str(b['urlUser']) } : {}),
164
+ ...(b['urlPassword'] !== undefined ? { urlPassword: str(b['urlPassword']) } : {}),
161
165
  ...(b['fields'] !== undefined ? { fields: b['fields'] } : {}),
162
166
  ...(ssl !== undefined ? { ssl } : {}),
163
167
  // 编辑已有连接时透传:服务端拼回已存机密(留空密码/url 未改动的测试语义)
@@ -178,6 +182,8 @@ async function route(req, res, service, path, q, opts) {
178
182
  return sendOk(res, service.updateConnection(id, {
179
183
  ...(b['name'] !== undefined ? { name: str(b['name']) } : {}),
180
184
  ...(b['url'] !== undefined ? { url: str(b['url']) } : {}),
185
+ ...(b['urlUser'] !== undefined ? { urlUser: str(b['urlUser']) } : {}),
186
+ ...(b['urlPassword'] !== undefined ? { urlPassword: str(b['urlPassword']) } : {}),
181
187
  ...(b['fields'] !== undefined ? { fields: b['fields'] } : {}),
182
188
  ...(b['clearUrl'] === true ? { clearUrl: true } : {}),
183
189
  ...(ssl !== undefined ? { ssl } : {}),
package/dist/manager.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { getAdapter } from './adapters/index.js';
2
2
  import { ChallengeStore, classifyStatement, } from './guard/index.js';
3
- import { normalizeProjectKey, } from './store/index.js';
3
+ import { mergeUrlCredentials, normalizeProjectKey, } from './store/index.js';
4
4
  import { runScriptInChild } from './script/runner.js';
5
5
  /* ---------------- 错误与结果类型 ---------------- */
6
6
  /** 携带机器可读 code 的业务错误(HTTP 层直接映射 {ok:false,error,code}) */
@@ -84,15 +84,24 @@ export class DbToolService {
84
84
  };
85
85
  let url = input.url;
86
86
  let fields = input.fields !== undefined ? { ...input.fields } : undefined;
87
+ let secretsUrl;
87
88
  if (input.connId) {
88
89
  // 已存连接:编辑时客户端不发旧机密(留空语义),这里从 secrets 拼回
89
90
  const sec = this.store.secrets.get(input.connId);
90
- if (url === undefined && sec?.url !== undefined)
91
- url = sec.url;
91
+ secretsUrl = sec?.url;
92
+ if (url === undefined && secretsUrl !== undefined)
93
+ url = secretsUrl;
92
94
  if (fields !== undefined && sec?.password !== undefined && fields.password === undefined) {
93
95
  fields.password = sec.password;
94
96
  }
95
97
  }
98
+ // URL 模式独立凭据注入(拼回已存 URL 之后;无凭据字段时 helper 原样返回,?? 保持不变)
99
+ url = mergeUrlCredentials({
100
+ url,
101
+ urlUser: input.urlUser,
102
+ urlPassword: input.urlPassword,
103
+ secretsUrl,
104
+ }) ?? url;
96
105
  if (url !== undefined)
97
106
  rc.url = url;
98
107
  if (fields !== undefined)
@@ -28,6 +28,45 @@ export function redactUrl(url) {
28
28
  const redacted = m[1] + userinfo.slice(0, colon + 1) + '***@' + authority.slice(at + 1);
29
29
  return redacted + url.slice(m[0].length);
30
30
  }
31
+ /** URL 模式独立凭据注入:把 urlUser/urlPassword 合并进 URL userinfo(8 库 scheme 均为标准 URL 形态)。
32
+ * - 两者均空 → 原样返回(url 可为 undefined,现状语义不变);
33
+ * - url 为 undefined → 有 secretsUrl(编辑场景已存真实 URL)以其为基底注入,否则凭据忽略返回 undefined;
34
+ * - url 含脱敏标记 ':***@'(编辑回填值)→ 有 secretsUrl 以其为基底整体替换,否则报错;
35
+ * - 注入用 WHATWG URL 的 username/password setter(自动 percent-encode '@:/?# ' 等特殊字符),
36
+ * 与适配器侧 decodeURIComponent / 驱动 RFC 3986 解码严格互逆;只填一项只覆盖一项
37
+ * (redis 'redis://:pass@host' 无用户名形态、oracle 必填用户名校验均兼容)。 */
38
+ export function mergeUrlCredentials(opts) {
39
+ const { url, urlUser, urlPassword, secretsUrl } = opts;
40
+ if (urlUser === undefined && urlPassword === undefined)
41
+ return url;
42
+ let base;
43
+ if (url === undefined) {
44
+ if (secretsUrl === undefined)
45
+ return undefined; // 无 URL 可注入,凭据忽略
46
+ base = secretsUrl;
47
+ }
48
+ else if (url.includes(':***@')) {
49
+ if (secretsUrl === undefined) {
50
+ throw new Error('URL 中含脱敏占位(***)且无已存真实 URL 可回退,请重新输入完整 URL 或清空用户名/密码框');
51
+ }
52
+ base = secretsUrl;
53
+ }
54
+ else {
55
+ base = url;
56
+ }
57
+ let u;
58
+ try {
59
+ u = new URL(base);
60
+ }
61
+ catch {
62
+ throw new Error('连接 URL 无效,无法注入用户名/密码');
63
+ }
64
+ if (urlUser !== undefined && urlUser !== '')
65
+ u.username = urlUser;
66
+ if (urlPassword !== undefined && urlPassword !== '')
67
+ u.password = urlPassword;
68
+ return u.toString();
69
+ }
31
70
  function splitPassword(fields) {
32
71
  if (!fields || typeof fields[PASSWORD_KEY] !== 'string') {
33
72
  return { clean: fields, password: undefined };
@@ -107,9 +146,15 @@ export class ConnectionStore {
107
146
  if (clean !== undefined)
108
147
  rec.fields = clean;
109
148
  const secretPatch = {};
110
- if (input.url !== undefined) {
111
- rec.urlSafe = redactUrl(input.url);
112
- secretPatch.url = input.url;
149
+ // URL 模式独立凭据注入(无凭据字段时 mergeUrlCredentials 原样返回,现状语义不变)
150
+ const finalUrl = mergeUrlCredentials({
151
+ url: input.url,
152
+ urlUser: input.urlUser,
153
+ urlPassword: input.urlPassword,
154
+ });
155
+ if (finalUrl !== undefined) {
156
+ rec.urlSafe = redactUrl(finalUrl);
157
+ secretPatch.url = finalUrl;
113
158
  }
114
159
  if (password !== undefined)
115
160
  secretPatch.password = password;
@@ -135,9 +180,17 @@ export class ConnectionStore {
135
180
  if (password !== undefined)
136
181
  this.secrets.set(id, { password });
137
182
  }
138
- if (patch.url !== undefined) {
139
- rec.urlSafe = redactUrl(patch.url);
140
- this.secrets.set(id, { url: patch.url });
183
+ // URL 模式独立凭据注入:url 未发/发脱敏回填值时以已存 secrets.url 为基底(update 场景);
184
+ // 无 url 且无凭据字段时 mergeUrlCredentials 返回 undefined,自然跳过(无需外层守卫)
185
+ const finalUrl = mergeUrlCredentials({
186
+ url: patch.url,
187
+ urlUser: patch.urlUser,
188
+ urlPassword: patch.urlPassword,
189
+ secretsUrl: this.secrets.get(id)?.url,
190
+ });
191
+ if (finalUrl !== undefined) {
192
+ rec.urlSafe = redactUrl(finalUrl);
193
+ this.secrets.set(id, { url: finalUrl });
141
194
  }
142
195
  if (patch.clearUrl && rec.urlSafe !== undefined) {
143
196
  delete rec.urlSafe;
@@ -18,7 +18,7 @@ import { GrantStore } from './grants.js';
18
18
  import { chmodBestEffort } from './io.js';
19
19
  import { SecretsBox } from './secrets.js';
20
20
  export { normalizeProjectKey } from './normalize.js';
21
- export { redactUrl } from './connections.js';
21
+ export { mergeUrlCredentials, redactUrl } from './connections.js';
22
22
  export { AuditLog, ConnectionStore, GrantStore, SecretsBox };
23
23
  export class DbToolStore {
24
24
  /** 数据目录:$DSH_HOME/db-tool */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-db-tool",
3
- "version": "1.3.2",
3
+ "version": "1.4.1",
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
6
  "license": "MIT",
@@ -29,8 +29,6 @@
29
29
  "build": "tsc -p tsconfig.build.json && node -e \"const fs=require('fs'); fs.mkdirSync('dist/script',{recursive:true}); fs.copyFileSync('lib/script/worker.cjs','dist/script/worker.cjs'); console.log('build OK')\" && node scripts/build-mongodb.mjs",
30
30
  "build:mongodb": "node scripts/build-mongodb.mjs",
31
31
  "prepack": "npm run build",
32
- "build:gaussdb": "powershell -NoProfile -ExecutionPolicy Bypass -File scripts/build-gaussdb.ps1",
33
- "build:gaussdb:sh": "bash scripts/build-gaussdb.sh",
34
32
  "patch:dsh": "powershell -NoProfile -ExecutionPolicy Bypass -File scripts/apply-dsh-hotfix.ps1",
35
33
  "patch:dsh:sh": "bash scripts/apply-dsh-hotfix.sh"
36
34
  },
@@ -48,6 +46,7 @@
48
46
  "dependencies": {
49
47
  "bson": "^7.2.0",
50
48
  "dmdb": "^1.0.52452",
49
+ "gaussdb-node": "^0.2.2",
51
50
  "mysql2": "^3.24.4",
52
51
  "oracledb": "^7.0.1",
53
52
  "pg": "^8.23.0",
@@ -73,9 +72,6 @@
73
72
  "@deepseek-ai/cordis": "^4.0.1",
74
73
  "dsh-better-sidebar": ">=0.12.0"
75
74
  },
76
- "allowScripts": {
77
- "oracledb@7.0.1": true
78
- },
79
75
  "files": [
80
76
  "cordis.patch.yml",
81
77
  "dist",
@@ -85,6 +81,7 @@
85
81
  "patches",
86
82
  "scripts",
87
83
  "vendor/mongodb-driver.cjs",
88
- "README.md"
84
+ "README.md",
85
+ "README_EN.md"
89
86
  ]
90
87
  }
@@ -1,72 +0,0 @@
1
- # Build the official GaussDB (openGauss) Node driver into vendor/.
2
- # PowerShell version (use when bash/WSL is unavailable on Windows).
3
- #
4
- # powershell -ExecutionPolicy Bypass -File scripts/build-gaussdb.ps1 # skip if built
5
- # powershell -ExecutionPolicy Bypass -File scripts/build-gaussdb.ps1 -Force # rebuild
6
- #
7
- # Equivalent to scripts/build-gaussdb.sh. Output: vendor/gaussdb-pg/packages/pg
8
- # (CJS, pg-like: { Pool, Client, ... }; pg lib requires '../../pg-protocol'/'../../pg-pool',
9
- # so the monorepo packages/ layout must be preserved)
10
- # Requires Node >= 16, npm >= 7. Source: gitcode.com/opengauss/openGauss-connector-nodejs (master latest).
11
- # NOTE: keep this file ASCII-only so Windows PowerShell 5.1 (GBK codepage) can parse it.
12
- param(
13
- [switch]$Force
14
- )
15
- $ErrorActionPreference = 'Stop'
16
-
17
- $RepoRoot = Split-Path -Parent $PSScriptRoot
18
- $Src = Join-Path $RepoRoot 'vendor\gaussdb-src'
19
- $Dst = Join-Path $RepoRoot 'vendor\gaussdb-pg'
20
-
21
- if (-not $Force -and (Test-Path (Join-Path $Dst 'packages\pg\package.json'))) {
22
- Write-Host "[build-gaussdb] $Dst already built, skip (use -Force to rebuild)"
23
- exit 0
24
- }
25
-
26
- if (Test-Path (Join-Path $Src '.git')) {
27
- Write-Host "[build-gaussdb] source exists, updating..."
28
- git -C $Src pull --ff-only
29
- if ($LASTEXITCODE -ne 0) { throw "git pull failed (exit $LASTEXITCODE)" }
30
- } else {
31
- Write-Host "[build-gaussdb] cloning openGauss-connector-nodejs..."
32
- if (Test-Path $Src) { Remove-Item -Recurse -Force $Src }
33
- New-Item -ItemType Directory -Force -Path (Join-Path $RepoRoot 'vendor') | Out-Null
34
- git clone https://gitcode.com/opengauss/openGauss-connector-nodejs $Src
35
- if ($LASTEXITCODE -ne 0) { throw "git clone failed (exit $LASTEXITCODE)" }
36
- }
37
-
38
- Write-Host "[build-gaussdb] npm install && npm run build..."
39
- Push-Location $Src
40
- try {
41
- npm install
42
- if ($LASTEXITCODE -ne 0) { throw "npm install failed (exit $LASTEXITCODE)" }
43
- npm run build
44
- if ($LASTEXITCODE -ne 0) { throw "npm run build failed (exit $LASTEXITCODE)" }
45
- } finally {
46
- Pop-Location
47
- }
48
-
49
- Write-Host "[build-gaussdb] copying packages layout -> vendor/gaussdb-pg/packages ..."
50
- if (Test-Path $Dst) { Remove-Item -Recurse -Force $Dst }
51
- New-Item -ItemType Directory -Force (Join-Path $Dst 'packages') | Out-Null
52
- Copy-Item -Recurse (Join-Path $Src 'packages\pg') (Join-Path $Dst 'packages\pg')
53
- Copy-Item -Recurse (Join-Path $Src 'packages\pg-protocol') (Join-Path $Dst 'packages\pg-protocol')
54
- Copy-Item -Recurse (Join-Path $Src 'packages\pg-pool') (Join-Path $Dst 'packages\pg-pool')
55
-
56
- $PgDir = Join-Path $Dst 'packages\pg'
57
- Write-Host "[build-gaussdb] installing runtime deps (incl. undeclared p-limit used by fork)..."
58
- Push-Location $PgDir
59
- try {
60
- npm install --omit=dev --no-audit --no-fund
61
- if ($LASTEXITCODE -ne 0) { throw "npm install failed (exit $LASTEXITCODE)" }
62
- npm install p-limit --omit=dev --no-audit --no-fund
63
- if ($LASTEXITCODE -ne 0) { throw "npm install p-limit failed (exit $LASTEXITCODE)" }
64
- } finally {
65
- Pop-Location
66
- }
67
-
68
- if (-not (Test-Path (Join-Path $PgDir 'package.json'))) {
69
- throw "build output missing package.json, check $Src\packages\pg layout"
70
- }
71
-
72
- Write-Host "[build-gaussdb] done: $PgDir"
@@ -1,53 +0,0 @@
1
- #!/usr/bin/env bash
2
- # 构建 GaussDB(openGauss)官方 Node 驱动 vendor 产物。
3
- #
4
- # 官方 openGauss-connector-nodejs 未发布 npm,需从源码构建:
5
- # bash scripts/build-gaussdb.sh # 已有产物则跳过(幂等)
6
- # bash scripts/build-gaussdb.sh --force # 强制重新拉取并构建
7
- # npm run build:gaussdb # 等价入口
8
- #
9
- # 手动步骤(脚本因网络等失败时可手动执行):
10
- # git clone https://gitcode.com/opengauss/openGauss-connector-nodejs vendor/gaussdb-src
11
- # cd vendor/gaussdb-src && npm install && npm run build
12
- # rm -rf vendor/gaussdb-pg && cp -r packages/pg vendor/gaussdb-pg
13
- # # 注意保留 vendor/gaussdb-pg/package.json 与 dist/
14
- #
15
- # 产物:vendor/gaussdb-pg/packages/pg(CJS,结构同 pg:{ Pool, Client, ... };
16
- # pg lib 内 require('../../pg-protocol'/'../../pg-pool') 需 packages/ 布局)
17
- set -euo pipefail
18
- cd "$(dirname "$0")/.."
19
-
20
- SRC=vendor/gaussdb-src
21
- DST=vendor/gaussdb-pg
22
- FORCE=0
23
- if [ "${1:-}" = "--force" ]; then FORCE=1; fi
24
-
25
- if [ "$FORCE" != "1" ] && [ -f "$DST/packages/pg/package.json" ]; then
26
- echo "[build-gaussdb] 已存在 $DST/packages/pg/package.json,跳过(--force 可强制重建)"
27
- exit 0
28
- fi
29
-
30
- if [ -d "$SRC/.git" ]; then
31
- echo "[build-gaussdb] 源码已存在,更新..."
32
- git -C "$SRC" pull --ff-only
33
- else
34
- echo "[build-gaussdb] 克隆 openGauss-connector-nodejs..."
35
- rm -rf "$SRC"
36
- mkdir -p vendor
37
- git clone https://gitcode.com/opengauss/openGauss-connector-nodejs "$SRC"
38
- fi
39
-
40
- echo "[build-gaussdb] 安装依赖并构建..."
41
- (cd "$SRC" && npm install && npm run build)
42
-
43
- echo "[build-gaussdb] 拷贝 packages 布局 -> $DST"
44
- rm -rf "$DST"
45
- mkdir -p "$DST/packages"
46
- cp -r "$SRC/packages/pg" "$DST/packages/pg"
47
- # pg lib 内 require('../../pg-protocol'/'../../pg-pool') 按 monorepo packages/ 布局解析
48
- cp -r "$SRC/packages/pg-protocol" "$DST/packages/pg-protocol"
49
- cp -r "$SRC/packages/pg-pool" "$DST/packages/pg-pool"
50
- # 运行依赖(含 fork 源码已用但 package.json 未声明的 p-limit)
51
- (cd "$DST/packages/pg" && npm install --omit=dev --no-audit --no-fund && npm install p-limit --omit=dev --no-audit --no-fund)
52
-
53
- echo "[build-gaussdb] 构建完成 -> $DST/packages/pg"