e10-data 0.1.8 → 0.1.10
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 +62 -3
- package/bin/e10-data.mjs +344 -136
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -9,7 +9,7 @@ npm i -g e10-data # 全局安装
|
|
|
9
9
|
e10-data --version # 验证
|
|
10
10
|
```
|
|
11
11
|
|
|
12
|
-
> 依赖:认证复用 `weaver-e10-login`
|
|
12
|
+
> 依赖:认证复用 `weaver-e10-login` 的登录态。**加解密全部由 `e10-login` 负责,本 CLI 不实现任何加解密**(auth 文件由 e10-login 自己的密钥体系加密:1.5.0 起为「每机随机密钥 + OS keychain」,早期版本为固定 seed,只有 e10-login 解得开)。CLI 通过 e10-login 的命令出口取明文凭据,优先级:**① `e10-login auth --json`**(推荐的正式契约)→ **② `e10-login whoami --json`**(其 JSON 带 `cookies` 时)→ **③ `e10-login whoami`**(文本解析,当前实际路径)→ **④ 明文镜像 `~/.workbuddy/e10-login-cache.md`**(兜底,命中时打印告警)。请先安装并登录 weaver-e10-login(`e10-login oidc`)。
|
|
13
13
|
|
|
14
14
|
## 命令总览
|
|
15
15
|
|
|
@@ -19,7 +19,7 @@ e10-data ds tables|fields|list|chart|stat|count 数据源(业务模块
|
|
|
19
19
|
e10-data doc count|list 文档查询(documentListCount + documentList)
|
|
20
20
|
e10-data noun list --skill <skillKey> 词库查询(可带 --source-type/--source-group/--source-table 三参数)
|
|
21
21
|
e10-data field id2name|name2id 关联字段翻译(testRemoteBrowser / complete)
|
|
22
|
-
e10-data conf get|batch|batch-file|resolve|resolve-file|add|add-file|update|delete 环境参数配置中心(chatBi config map,替代 config-resolve 脚本 URL 直连;confType 默认按 CONN_/DS_/WF_ 前缀推导,可 --type
|
|
22
|
+
e10-data conf get|batch|batch-file|group|group-file|resolve|resolve-file|add|add-file|update|delete 环境参数配置中心(chatBi config map,替代 config-resolve 脚本 URL 直连;confType 默认按 CONN_/DS_/WF_/API_ 前缀推导,可 --type 显式;confType=api 时支持 confGroup 分组,见下)
|
|
23
23
|
e10-data exp status|manifest|commits|file|pull|init|commit|lock|release|rollback 专家包版本管理
|
|
24
24
|
e10-data attend statis 考勤假期余额统计 getAttendInfoStatis(hr-attendance 病假/事假等,v0.1.5 新增)
|
|
25
25
|
```
|
|
@@ -29,7 +29,7 @@ e10-data attend statis 考勤假期余额统计 getAttendInfoStatis(hr-atte
|
|
|
29
29
|
- `--json`:原样输出接口 JSON(供脚本/Agent 消费);默认人读输出。
|
|
30
30
|
- 复杂 JSON/中文参数:推荐 `@文件` 承载(如 `--dsl @q.json`、`--where @cond.json`)。
|
|
31
31
|
- ⚠️ 大整数精度(自动处理):E10 的 ID 多为 19 位,超过 JS 安全整数(2^53-1≈9e15)。CLI 解析 JSON 时**自动把超范围裸整数转成字符串**再发请求/输出(实测后端接受字符串做 =/IN/BETWEEN 数值比较),无需手动加引号,也不误伤字符串正文。
|
|
32
|
-
-
|
|
32
|
+
- 认证:**加解密由 `e10-login` 负责(本 CLI 不参与)**。凭据出口优先级:`e10-login auth --json` → `e10-login whoami --json` → `e10-login whoami`(文本)→ 明文镜像 `~/.workbuddy/e10-login-cache.md`(兜底,命中时告警)。命中的出口会记入 `~/.e10-cli/.e10-data-auth-mode`(只存模式名、不存凭据),下次优先复用。环境变量:`E10_DATA_AUTH_PROBE=1` 强制重新探测、`E10_DATA_AUTH_MODE=<模式名>` 手动指定、`E10_DATA_QUIET=1` 抑制告警(告警只走 stderr)。
|
|
33
33
|
- 版本更新:`npm i -g e10-data@latest`;查看源上最新 `npm view e10-data version`。
|
|
34
34
|
|
|
35
35
|
## 用法示例
|
|
@@ -47,12 +47,71 @@ e10-data conf get CONN_EC_EDCAPP # 读配置
|
|
|
47
47
|
e10-data conf resolve '{"connId":"#{CONN_EC_EDCAPP}"}' # 占位符替换为真实值
|
|
48
48
|
e10-data conf add CONN_EC_EDCAPP --value 10 --name "说明" --attrs '{"schema":"ec_edcapp"}'
|
|
49
49
|
e10-data conf delete CONN_EC_EDCAPP
|
|
50
|
+
# confType=api + confGroup 分组(v0.1.9 新增)
|
|
51
|
+
e10-data conf group api workflow # 按 类型+分组 批量查全部配置
|
|
52
|
+
e10-data conf get GET_WORKFLOW_LIST --type api --group workflow # 单查(组内定位)
|
|
53
|
+
e10-data conf add GET_WORKFLOW_LIST --type api --group workflow --value /api/workflow/getList --name 查询流程列表 --attrs '{"request":{"pageNo":"页码"},"response":{"list":"流程列表"}}'
|
|
54
|
+
e10-data conf add-file @api-items.json # 批量新增(元素含 confGroup,见下)
|
|
55
|
+
e10-data conf update GET_WORKFLOW_LIST --type api --group workflow --value /api/workflow/queryList
|
|
56
|
+
e10-data conf delete GET_WORKFLOW_LIST --type api --group workflow
|
|
50
57
|
e10-data exp status --expert data-assistant
|
|
51
58
|
e10-data exp pull --expert data-assistant --dir <本地专家包目录> [--dry-run] [--force]
|
|
52
59
|
e10-data attend statis # 查当前登录用户当月假期余额(病假/事假/年假)
|
|
53
60
|
e10-data attend statis --user 6264843388578732287 --begin 2026-09-01 --end 2026-09-30
|
|
54
61
|
```
|
|
55
62
|
|
|
63
|
+
## conf 配置中心与 confGroup 分组(v0.1.9 新增)
|
|
64
|
+
|
|
65
|
+
`conf` 系列对接 chatBi config map 接口,`confGroup` 是**可选**的二级分组维度:后端在 `confType=api` 时按 `confType + confGroup + confKey` 定位配置(如 `api/workflow/GET_WORKFLOW_LIST`),用于把同类 API 定义归档到同一分组。
|
|
66
|
+
|
|
67
|
+
**下发规则(重要)**:只有 `confType` 为 `api`(或显式传了 `--group`)时,CLI 才会在请求体/查询串里带上 `confGroup`;其他 confType 一律不带该字段,避免污染既有配置类型。
|
|
68
|
+
|
|
69
|
+
| 操作 | 命令 | 对应接口 |
|
|
70
|
+
|---|---|---|
|
|
71
|
+
| 新增(批量,可含分组) | `conf add <key> --value v [--type api] [--group g]` / `conf add-file @items.json` | `POST /api/dw/newetl/chatBi/batchAddChatBiConfigMap` |
|
|
72
|
+
| 单查 | `conf get <key> [--type api] [--group g]` | `GET /api/dw/newetl/chatBi/getChatBiConfigMap?confType=&confGroup=&confKey=` |
|
|
73
|
+
| 批量查(按 key) | `conf batch <k1> <k2> ...` / `conf batch-file @items.json` | `POST /api/dw/newetl/chatBi/batchGetChatBiConfigMap` |
|
|
74
|
+
| 批量查(按类型+分组) | `conf group <confType> <confGroup> [--key k]` / `conf group-file @items.json` | `POST /api/dw/newetl/chatBi/batchGetChatBiConfigMapByTypeAndGroup` |
|
|
75
|
+
| 更新 | `conf update <key> [--value v] [--name n] [--attrs a] [--group g]` | `POST /api/dw/newetl/chatBi/updateChatBiConfigMap` |
|
|
76
|
+
| 删除 | `conf delete <key> [--type api] [--group g]` | `DELETE /api/dw/newetl/chatBi/deleteChatBiConfigMap?confType=&confGroup=&confKey=` |
|
|
77
|
+
|
|
78
|
+
批量新增文件 `api-items.json`(`confType=api` 时元素带 `confGroup`):
|
|
79
|
+
|
|
80
|
+
```json
|
|
81
|
+
[
|
|
82
|
+
{
|
|
83
|
+
"confType": "api",
|
|
84
|
+
"confGroup": "workflow",
|
|
85
|
+
"confKey": "GET_WORKFLOW_LIST",
|
|
86
|
+
"confValue": "/api/workflow/getList",
|
|
87
|
+
"confName": "查询流程列表",
|
|
88
|
+
"confAttrs": "{\"request\":{\"pageNo\":\"页码\",\"pageSize\":\"每页数量\"},\"response\":{\"list\":\"流程列表\"}}"
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
"confType": "api",
|
|
92
|
+
"confGroup": "workflow",
|
|
93
|
+
"confKey": "GET_WORKFLOW_DETAIL",
|
|
94
|
+
"confValue": "/api/workflow/getDetail",
|
|
95
|
+
"confName": "查询流程详情",
|
|
96
|
+
"confAttrs": "{\"request\":{\"id\":\"流程ID\"},\"response\":{\"id\":\"流程ID\",\"name\":\"流程名称\"}}"
|
|
97
|
+
}
|
|
98
|
+
]
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
e10-data conf add-file @api-items.json # 批量新增(逐条判重:同 组+键 已存在则跳过)
|
|
103
|
+
e10-data conf group api workflow # 取回 workflow 分组下全部 API 配置
|
|
104
|
+
e10-data conf group-file @groups.json # 多分组一次取回,@groups.json = [{"confType":"api","confGroup":"workflow"}]
|
|
105
|
+
e10-data conf update GET_WORKFLOW_LIST --type api --group workflow --value /api/workflow/queryList
|
|
106
|
+
e10-data conf delete GET_WORKFLOW_LIST --type api --group workflow
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
补充说明:
|
|
110
|
+
|
|
111
|
+
- `confKey` 前缀推导 confType:`CONN_→conn`、`DS_→ds`、`WF_→wf`、`API_→api`;其余类型用 `--type` 显式指定(`--type` 与 `--group` 可同时使用)。
|
|
112
|
+
- 同一 `confKey` 可存在于多个 `confGroup`;`conf batch` / `batch-file` 的返回映射在带分组时以 `组|键` 为键,不会互相覆盖。
|
|
113
|
+
- `--json` 输出:`conf group` 返回配置数组(带 `--key` 时返回单条),`conf batch` 返回映射对象。
|
|
114
|
+
|
|
56
115
|
## ds --where 过滤条件(v0.1.8 起支持树形 OR)
|
|
57
116
|
|
|
58
117
|
`e10-data ds list|chart|stat|count --where` 接收**条件数组**或**完整条件树**,均可递归嵌套表达 AND/OR:
|
package/bin/e10-data.mjs
CHANGED
|
@@ -9,12 +9,15 @@
|
|
|
9
9
|
* e10-data doc count|list 文档查询 documentListCount / documentList
|
|
10
10
|
* e10-data noun list 词库 getChatDocsBySkillKey
|
|
11
11
|
* e10-data field id2name|name2id 关联字段翻译 testRemoteBrowser / complete
|
|
12
|
-
* e10-data conf get|batch|batch-file|resolve|resolve-file|add|add-file|update|delete
|
|
13
|
-
* 环境参数配置中心(chatBi config map,替代 config-resolve 脚本 URL
|
|
12
|
+
* e10-data conf get|batch|batch-file|group|group-file|resolve|resolve-file|add|add-file|update|delete
|
|
13
|
+
* 环境参数配置中心(chatBi config map,替代 config-resolve 脚本 URL 直连;
|
|
14
|
+
* confType=api 时支持 confGroup 分组二级定位,见 --group)
|
|
14
15
|
* e10-data attend statis 考勤假期余额统计 getAttendInfoStatis(hr-attendance 病假/事假,2026-09-07)
|
|
15
16
|
*
|
|
16
17
|
* 通用:--json 原样输出;复杂 JSON 参数支持 @文件 或 内联(中文走 @文件 更稳)
|
|
17
|
-
*
|
|
18
|
+
* 认证:加解密全部由 e10-login 负责(本 CLI 不实现任何加解密)。凭据出口优先级:
|
|
19
|
+
* ① e10-login auth --json → ② e10-login whoami --json → ③ e10-login whoami(文本解析)
|
|
20
|
+
* → ④ 明文镜像 ~/.workbuddy/e10-login-cache.md(兜底,命中时告警);均不可用时提示先 e10-login oidc 登录
|
|
18
21
|
*
|
|
19
22
|
* 用法示例:
|
|
20
23
|
* node e10-data.mjs query --conn 1144919996804022272 --model dw --schema dw --dsl @q.json
|
|
@@ -28,6 +31,7 @@ import fs from "node:fs";
|
|
|
28
31
|
import os from "node:os";
|
|
29
32
|
import path from "node:path";
|
|
30
33
|
import crypto from "node:crypto";
|
|
34
|
+
import { spawnSync } from "node:child_process";
|
|
31
35
|
import { fileURLToPath } from "node:url";
|
|
32
36
|
|
|
33
37
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
@@ -39,70 +43,148 @@ function cliVersion() {
|
|
|
39
43
|
const UA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/151.0.0.0 Safari/537.36";
|
|
40
44
|
const EB_BIZ_ID = "10000000000000000";
|
|
41
45
|
|
|
42
|
-
// ----------------
|
|
43
|
-
//
|
|
44
|
-
//
|
|
45
|
-
//
|
|
46
|
-
|
|
47
|
-
|
|
46
|
+
// ---------------- 认证(加解密全部交由 e10-login,本 CLI 不再实现任何加解密)----------------
|
|
47
|
+
// 背景:auth 文件由 e10-login 自己的密钥体系加密 —— e10-login 1.5.0 起为「每机随机密钥 + OS keychain
|
|
48
|
+
// (@napi-rs/keyring,service=e10-cli / account=auth-key;keychain 不可用才落 ~/.e10-cli/.key)」,
|
|
49
|
+
// 早期版本为固定 seed。**只有 e10-login 解得开**,所以本 CLI 只负责「向 e10-login 要明文凭据」,
|
|
50
|
+
// 不再自行解密 —— e10-login 以后更换密钥体系 / 算法 / profile 机制,本 CLI 都无需改动。
|
|
51
|
+
// 凭据出口优先级(命中即返回;命中的"模式名"会记入 ~/.e10-cli/.e10-data-auth-mode,只存模式、不存凭据):
|
|
52
|
+
// ① e10-login auth --json 正式契约(推荐 e10-login 侧新增;一旦可用自动优先)
|
|
53
|
+
// ② e10-login whoami --json 其 JSON 输出带 cookies 字段时可用
|
|
54
|
+
// ③ e10-login whoami 文本输出解析(当前 e10-login 1.5.0 的实际可用路径)
|
|
55
|
+
// ④ ~/.workbuddy/e10-login-cache.md 明文镜像 —— 最后兜底,命中时打印告警(该文件可能已过期)
|
|
56
|
+
// 环境变量:E10_DATA_AUTH_PROBE=1 强制重新探测优先级;E10_DATA_AUTH_MODE=<模式名> 手动指定;
|
|
57
|
+
// E10_DATA_QUIET=1 抑制告警输出(告警一律走 stderr,不污染 --json 的 stdout)。
|
|
58
|
+
const AUTH_MODE_FILE = path.join(os.homedir(), ".e10-cli", ".e10-data-auth-mode");
|
|
59
|
+
|
|
60
|
+
/** 定位当前 profile 的 auth 文件(仅用于"是否已登录"的判断与报错提示,本 CLI 不读其内容做解密) */
|
|
61
|
+
function authFilePath() {
|
|
62
|
+
const home = os.homedir();
|
|
63
|
+
let profile = "default";
|
|
48
64
|
try {
|
|
49
|
-
const
|
|
50
|
-
const
|
|
51
|
-
if (combined.length < IV + TAG + 1) return null;
|
|
52
|
-
const key = crypto.createHash("sha256").update(E10_AUTH_SEED).digest();
|
|
53
|
-
const decipher = crypto.createDecipheriv("aes-256-gcm", key, combined.subarray(0, IV), { authTagLength: TAG });
|
|
54
|
-
decipher.setAuthTag(combined.subarray(combined.length - TAG));
|
|
55
|
-
return JSON.parse(Buffer.concat([decipher.update(combined.subarray(IV, combined.length - TAG)), decipher.final()]).toString("utf-8"));
|
|
56
|
-
} catch { return null; }
|
|
57
|
-
}
|
|
58
|
-
function parseAuthPayload(raw) {
|
|
59
|
-
try { // 兼容两种形态:加密 wrapper({enc:true,data:base64})与明文 JSON({cookies,userId,...})
|
|
60
|
-
const obj = JSON.parse(raw);
|
|
61
|
-
if (obj && obj.enc === true && typeof obj.data === "string") return decryptAuthWrapper(obj);
|
|
62
|
-
if (obj && obj.cookies && (obj.userId || obj.cookies.ETEAMSID)) return obj;
|
|
65
|
+
const activeFile = path.join(home, ".e10-cli", "profile");
|
|
66
|
+
if (fs.existsSync(activeFile)) { const n = fs.readFileSync(activeFile, "utf-8").trim(); if (n) profile = n; }
|
|
63
67
|
} catch {}
|
|
64
|
-
return
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
if (c && !/langType=/.test(c)) c = "langType=zh_CN; " + c;
|
|
71
|
-
if (c && agent && !/agentType=/.test(c)) c += "; agentType=" + agent;
|
|
72
|
-
if (c && !/isAgent=/.test(c)) c += "; isAgent=true";
|
|
73
|
-
if (!c && eteamsid) c = "langType=zh_CN; ETEAMSID=" + eteamsid + "; agentType=" + agent + "; isAgent=true";
|
|
74
|
-
return { eteamsid, cookie: c };
|
|
75
|
-
}
|
|
76
|
-
function authFromE10Cli() { // ① 权威源:~/.e10-cli/profiles/<active>/auth
|
|
68
|
+
return { profile, file: path.join(home, ".e10-cli", "profiles", profile, "auth") };
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
let lastLoginDiag = "";
|
|
72
|
+
/** 调用 e10-login(凭据的唯一来源);失败返回 null,并把诊断信息留在 lastLoginDiag */
|
|
73
|
+
function runLoginCli(args) {
|
|
77
74
|
try {
|
|
78
|
-
const
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
const data = parseAuthPayload(fs.readFileSync(p, "utf-8"));
|
|
87
|
-
if (!data || !data.cookies || !data.cookies.ETEAMSID || !data.baseUrl) return null;
|
|
88
|
-
const { eteamsid, cookie } = cookieFromAuthData(data);
|
|
89
|
-
return { baseUrl: data.baseUrl, eteamsid, cookie, userId: data.userId || "", tenantKey: data.tenantKey || "", profile };
|
|
90
|
-
} catch { return null; }
|
|
75
|
+
const r = spawnSync("e10-login", args, { shell: true, encoding: "utf-8", timeout: 20000, windowsHide: true });
|
|
76
|
+
const out = (r.stdout || "").trim();
|
|
77
|
+
if (r.error || r.status !== 0) {
|
|
78
|
+
lastLoginDiag = ((r.stderr || "") + out).trim().split(/\r?\n/).slice(0, 2).join(" / ") || (r.error ? String(r.error.message) : "exit=" + r.status);
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
return out || null;
|
|
82
|
+
} catch (e) { lastLoginDiag = String((e && e.message) || e); return null; }
|
|
91
83
|
}
|
|
92
|
-
|
|
84
|
+
|
|
85
|
+
/** 归一化 + 校验(缺 baseUrl / eteamsid / cookie 一律视为该出口不可用,继续下一个出口) */
|
|
86
|
+
function normalizeAuth(o) {
|
|
87
|
+
if (!o) return null;
|
|
88
|
+
const baseUrl = String(o.baseUrl || "").trim().replace(/\/+$/, "");
|
|
89
|
+
const eteamsid = String(o.eteamsid || "").trim();
|
|
90
|
+
const cookie = String(o.cookie || "").trim();
|
|
91
|
+
if (!baseUrl || !eteamsid || !cookie) return null;
|
|
92
|
+
if (/^\((未知|空)\)$/.test(eteamsid) || /^\((未知|空)\)$/.test(cookie)) return null;
|
|
93
|
+
return { baseUrl, eteamsid, cookie, userId: String(o.userId || "").trim(), tenantKey: String(o.tenantKey || "").trim(), source: o.source };
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** ① e10-login auth --json(正式契约:{v,baseUrl,eteamsId,cookie,userAgent,userId,tenantKey,agentType,profile}) */
|
|
97
|
+
function providerAuthJson() {
|
|
98
|
+
const out = runLoginCli(["auth", "--json"]);
|
|
99
|
+
if (!out) return null;
|
|
100
|
+
let j; try { j = JSON.parse(out); } catch { return null; }
|
|
101
|
+
if (!j || typeof j !== "object" || Array.isArray(j)) return null;
|
|
102
|
+
return normalizeAuth({
|
|
103
|
+
baseUrl: j.baseUrl || j.base_url,
|
|
104
|
+
eteamsid: j.eteamsId || j.eteamsid,
|
|
105
|
+
cookie: typeof j.cookie === "string" ? j.cookie : "",
|
|
106
|
+
userId: j.userId || j.employeeId,
|
|
107
|
+
tenantKey: j.tenantKey,
|
|
108
|
+
source: "e10-login auth --json",
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** ② e10-login whoami --json(需其 JSON 带 cookies 字符串;当前 1.5.0 未带 → 自动落到 ③) */
|
|
113
|
+
function providerWhoamiJson() {
|
|
114
|
+
const out = runLoginCli(["whoami", "--json"]);
|
|
115
|
+
if (!out) return null;
|
|
116
|
+
let j; try { j = JSON.parse(out); } catch { return null; }
|
|
117
|
+
if (!j || typeof j !== "object" || Array.isArray(j)) return null;
|
|
118
|
+
const cookie = typeof j.cookies === "string" ? j.cookies : (typeof j.cookie === "string" ? j.cookie : "");
|
|
119
|
+
return normalizeAuth({
|
|
120
|
+
baseUrl: j.baseUrl,
|
|
121
|
+
eteamsid: j.eteamsId || j.eteamsid,
|
|
122
|
+
cookie,
|
|
123
|
+
userId: j.employeeId || j.userId,
|
|
124
|
+
tenantKey: j.tenantKey,
|
|
125
|
+
source: "e10-login whoami --json",
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/** ③ e10-login whoami(文本输出解析:employeeId / tenantKey / baseUrl / ETEAMSID / cookies / User-Agent) */
|
|
130
|
+
function providerWhoamiText() {
|
|
131
|
+
const out = runLoginCli(["whoami"]);
|
|
132
|
+
if (!out) return null;
|
|
133
|
+
const m = {};
|
|
134
|
+
for (const line of out.split(/\r?\n/)) {
|
|
135
|
+
const x = line.match(/^\s*([A-Za-z][A-Za-z0-9_-]*)\s*:\s*(.+?)\s*$/);
|
|
136
|
+
if (x) m[x[1].toLowerCase()] = x[2];
|
|
137
|
+
}
|
|
138
|
+
return normalizeAuth({
|
|
139
|
+
baseUrl: m["baseurl"],
|
|
140
|
+
eteamsid: m["eteamsid"],
|
|
141
|
+
cookie: m["cookies"],
|
|
142
|
+
userId: m["employeeid"] || m["userid"],
|
|
143
|
+
tenantKey: m["tenantkey"],
|
|
144
|
+
source: "e10-login whoami(文本)",
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/** ④ 明文镜像(最后兜底:不涉及任何解密,但可能已过期 → 命中时告警) */
|
|
149
|
+
function providerCacheMd() {
|
|
93
150
|
const p = path.join(os.homedir(), ".workbuddy", "e10-login-cache.md");
|
|
94
151
|
if (!fs.existsSync(p)) return null;
|
|
95
152
|
const txt = fs.readFileSync(p, "utf-8");
|
|
96
153
|
const get = (key) => {
|
|
97
|
-
const
|
|
98
|
-
return
|
|
154
|
+
const mm = txt.match(new RegExp("\\*\\*" + key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&") + "\\*\\*\\s*[::]\\s*`?([^`\\n]+)`?"));
|
|
155
|
+
return mm ? mm[1].trim() : "";
|
|
99
156
|
};
|
|
100
|
-
const
|
|
101
|
-
|
|
102
|
-
if (!
|
|
103
|
-
return
|
|
157
|
+
const eteamsid = get("ETEAMSID");
|
|
158
|
+
let cookie = get("完整 Cookie 串");
|
|
159
|
+
if (!cookie && eteamsid) cookie = "langType=zh_CN; ETEAMSID=" + eteamsid + "; agentType=workbuddy; isAgent=true";
|
|
160
|
+
return normalizeAuth({
|
|
161
|
+
baseUrl: get("baseUrl"), eteamsid, cookie,
|
|
162
|
+
userId: get("userId") || get("employeeId"), tenantKey: get("tenantKey"),
|
|
163
|
+
source: "明文镜像 ~/.workbuddy/e10-login-cache.md(可能已过期)",
|
|
164
|
+
});
|
|
104
165
|
}
|
|
105
|
-
|
|
166
|
+
|
|
167
|
+
const AUTH_PROVIDERS = [
|
|
168
|
+
["auth-json", providerAuthJson],
|
|
169
|
+
["whoami-json", providerWhoamiJson],
|
|
170
|
+
["whoami-text", providerWhoamiText],
|
|
171
|
+
["cache-md", providerCacheMd],
|
|
172
|
+
];
|
|
173
|
+
/** 兜底出口不进记忆:否则 e10-login 偶发不可用一次,就会把"用镜像"永久设为首选、次次告警 */
|
|
174
|
+
const AUTH_FALLBACK_PROVIDERS = new Set(["cache-md"]);
|
|
175
|
+
|
|
176
|
+
function readAuthModeMemo() {
|
|
177
|
+
try {
|
|
178
|
+
const s = fs.readFileSync(AUTH_MODE_FILE, "utf-8").trim();
|
|
179
|
+
return AUTH_PROVIDERS.some(([n]) => n === s) ? s : null;
|
|
180
|
+
} catch { return null; }
|
|
181
|
+
}
|
|
182
|
+
function writeAuthModeMemo(name) {
|
|
183
|
+
try { fs.mkdirSync(path.dirname(AUTH_MODE_FILE), { recursive: true }); fs.writeFileSync(AUTH_MODE_FILE, name, "utf-8"); } catch {}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/** 凭据取自 e10-login 时,顺手把明文镜像刷新到最新(保持镜像可用;镜像不参与正常取数) */
|
|
187
|
+
function syncCacheMd(auth) {
|
|
106
188
|
try {
|
|
107
189
|
const p = path.join(os.homedir(), ".workbuddy", "e10-login-cache.md");
|
|
108
190
|
if (!fs.existsSync(p) || !/🔐 登录信息缓存/.test(fs.readFileSync(p, "utf-8"))) return; // 非标准缓存文件不动它
|
|
@@ -116,16 +198,47 @@ function syncCacheMd(auth) { // 读到权威源后自动回写明文镜像(内
|
|
|
116
198
|
txt = n;
|
|
117
199
|
};
|
|
118
200
|
rep("baseUrl", auth.baseUrl); rep("ETEAMSID", auth.eteamsid); rep("完整 Cookie 串", auth.cookie); rep("userId", auth.userId || ""); rep("tenantKey", auth.tenantKey || "");
|
|
119
|
-
rep("缓存记录时间", ts + "(e10-data
|
|
201
|
+
rep("缓存记录时间", ts + "(e10-data 自动同步,凭据来源:" + (auth.source || "") + ")");
|
|
120
202
|
if (changed) fs.writeFileSync(p, txt, "utf-8");
|
|
121
203
|
} catch {}
|
|
122
204
|
}
|
|
205
|
+
|
|
206
|
+
/** 取凭据:按优先级依次尝试各出口;命中的模式会被记住(下次优先尝试它,省一次进程调用) */
|
|
123
207
|
function loadAuth() {
|
|
124
|
-
const
|
|
125
|
-
|
|
126
|
-
const
|
|
127
|
-
|
|
128
|
-
|
|
208
|
+
const { profile, file } = authFilePath();
|
|
209
|
+
const override = (process.env.E10_DATA_AUTH_MODE || "").trim();
|
|
210
|
+
const memo = override || (process.env.E10_DATA_AUTH_PROBE ? null : readAuthModeMemo());
|
|
211
|
+
const order = memo
|
|
212
|
+
? AUTH_PROVIDERS.filter(([n]) => n === memo).concat(AUTH_PROVIDERS.filter(([n]) => n !== memo))
|
|
213
|
+
: AUTH_PROVIDERS;
|
|
214
|
+
|
|
215
|
+
const diag = [];
|
|
216
|
+
for (const [name, fn] of order) {
|
|
217
|
+
let got = null;
|
|
218
|
+
lastLoginDiag = "";
|
|
219
|
+
try { got = fn(); } catch (e) { diag.push(name + ": " + ((e && e.message) || e)); }
|
|
220
|
+
if (got) {
|
|
221
|
+
if (name !== memo && !AUTH_FALLBACK_PROVIDERS.has(name)) writeAuthModeMemo(name);
|
|
222
|
+
got.profile = profile;
|
|
223
|
+
if (name === "cache-md") {
|
|
224
|
+
if (!process.env.E10_DATA_QUIET) {
|
|
225
|
+
console.error("[e10-data][warn] 凭据来源=" + got.source + ";正常应由 e10-login 提供凭据,请检查 `e10-login whoami` 是否能输出 cookies。");
|
|
226
|
+
}
|
|
227
|
+
} else {
|
|
228
|
+
syncCacheMd(got); // 凭据来自 e10-login → 顺便把镜像刷新到最新
|
|
229
|
+
}
|
|
230
|
+
return got;
|
|
231
|
+
}
|
|
232
|
+
if (lastLoginDiag) diag.push(name + ": " + lastLoginDiag);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
throw new Error(
|
|
236
|
+
"未取得登录凭据(解密由 e10-login 负责,本 CLI 不参与)。请依次检查:\n" +
|
|
237
|
+
" ① e10-login 是否可用:`e10-login --version`(缺失 → `npm i -g e10-login`);`e10-login whoami` 应能输出 cookies\n" +
|
|
238
|
+
" ② 是否已登录 / 登录态是否失效:`e10-login oidc` 重新登录(当前 profile: " + profile + ")\n" +
|
|
239
|
+
" ③ auth 文件:" + (fs.existsSync(file) ? "存在" : "不存在") + "(" + file + ")—— 其加解密由 e10-login 负责\n" +
|
|
240
|
+
(diag.length ? " 探测诊断:" + diag.join(" | ") + "\n" : "")
|
|
241
|
+
);
|
|
129
242
|
}
|
|
130
243
|
const auth = loadAuth();
|
|
131
244
|
const BASE = auth.baseUrl.replace(/\/+$/, "");
|
|
@@ -134,7 +247,11 @@ const USER = { id: auth.userId || "", tenantKey: auth.tenantKey || "" };
|
|
|
134
247
|
// ---------------- HTTP ----------------
|
|
135
248
|
function checkAuthFail(resp, text, j) {
|
|
136
249
|
if (resp.status === 401 || (j && String(j.code) === "-1" && /token|登录|失效/i.test(String(j.msg || "")))) {
|
|
137
|
-
|
|
250
|
+
const from = (auth && auth.source) || "未知";
|
|
251
|
+
const hint = /镜像/.test(from)
|
|
252
|
+
? "当前用的是明文镜像兜底凭据(可能已过期),请先确认 `e10-login whoami` 能正常导出 cookies,或执行 `e10-login oidc` 重新登录"
|
|
253
|
+
: "凭据由 e10-login 提供,请执行 `e10-login oidc` 重新登录";
|
|
254
|
+
throw new Error("登录态失效(" + (j.msg || resp.status) + "):凭据来源=" + from + ";" + hint);
|
|
138
255
|
}
|
|
139
256
|
}
|
|
140
257
|
async function postJson(endpoint, bodyObj, extraHeaders = {}) {
|
|
@@ -265,6 +382,11 @@ function readJsonArg(val, label) {
|
|
|
265
382
|
return parseJsonSafe(raw, label);
|
|
266
383
|
}
|
|
267
384
|
function need(arg, args) { if (!args[arg]) throw new Error("缺少必填参数 --" + arg); return args[arg]; }
|
|
385
|
+
// 文件/JSON 参数取值:兼容 --flag <v> 与位置参数 <v>(如 conf add-file @items.json / --add-file @items.json)
|
|
386
|
+
function flagOrPos(args, flag, keys) {
|
|
387
|
+
if (args[flag] !== undefined) return args[flag];
|
|
388
|
+
return keys && keys.length ? keys[0] : undefined;
|
|
389
|
+
}
|
|
268
390
|
|
|
269
391
|
// ---------------- 展示 ----------------
|
|
270
392
|
function fmt(v) {
|
|
@@ -553,13 +675,21 @@ async function cmdFieldName2id(args, json) {
|
|
|
553
675
|
}
|
|
554
676
|
|
|
555
677
|
// ---------------- conf:环境参数配置中心(chatBi config map) ----------------
|
|
556
|
-
// 覆盖 config-resolve 原 resolve_config.py 的 URL 直连调用:get/batch/batch-file/resolve/resolve-file
|
|
678
|
+
// 覆盖 config-resolve 原 resolve_config.py 的 URL 直连调用:get/batch/batch-file/group/group-file/resolve/resolve-file
|
|
557
679
|
// 与管理 add/add-file/update/delete。认证复用进程内登录缓存(完整 Cookie + eteamsid,同 query/ds/doc),
|
|
558
680
|
// 无内置令牌;baseUrl 取登录缓存(与 executeQueryDsl 同域)。confType 默认由 key 前缀推导
|
|
559
|
-
// (CONN_→conn / DS_→ds / WF_→wf),可用 --type 显式指定任意大类(如 data-source-query / system-config)。
|
|
560
|
-
|
|
681
|
+
// (CONN_→conn / DS_→ds / WF_→wf / API_→api),可用 --type 显式指定任意大类(如 data-source-query / system-config)。
|
|
682
|
+
//
|
|
683
|
+
// v0.1.9 confGroup(配置分组,可选):
|
|
684
|
+
// 后端在 confType=api 时支持按分组二次分层(如 api/workflow、api/hr),定位维度变为
|
|
685
|
+
// 「confType + confGroup + confKey」。本 CLI 统一用 --group 传分组:
|
|
686
|
+
// · confType 为 api(或显式传了 --group)时,新增/更新/删除/单查 才下发 confGroup 字段,
|
|
687
|
+
// 其他 confType 一律不带该字段,避免污染既有(无分组列)的配置类型。
|
|
688
|
+
// · conf group <confType> <confGroup> 走 batchGetChatBiConfigMapByTypeAndGroup 按类型+分组批量取回。
|
|
689
|
+
const CONF_PREFIX_TYPE = [["CONN_", "conn"], ["DS_", "ds"], ["WF_", "wf"], ["API_", "api"]];
|
|
561
690
|
const CONF_GET_PATH = "/api/dw/newetl/chatBi/getChatBiConfigMap";
|
|
562
691
|
const CONF_BATCH_PATH = "/api/dw/newetl/chatBi/batchGetChatBiConfigMap";
|
|
692
|
+
const CONF_BATCH_GROUP_PATH = "/api/dw/newetl/chatBi/batchGetChatBiConfigMapByTypeAndGroup";
|
|
563
693
|
const CONF_ADD_PATH = "/api/dw/newetl/chatBi/batchAddChatBiConfigMap";
|
|
564
694
|
const CONF_UPDATE_PATH = "/api/dw/newetl/chatBi/updateChatBiConfigMap";
|
|
565
695
|
const CONF_DELETE_PATH = "/api/dw/newetl/chatBi/deleteChatBiConfigMap";
|
|
@@ -567,7 +697,25 @@ const CONF_TOKEN_RE = /#\{([A-Z][A-Z0-9_]{2,})\}/g;
|
|
|
567
697
|
function confKeyType(key, explicitType) {
|
|
568
698
|
if (explicitType) return explicitType;
|
|
569
699
|
for (const [p, t] of CONF_PREFIX_TYPE) if (key.startsWith(p)) return t;
|
|
570
|
-
throw new Error("无法由 confKey 推导 confType(需 CONN_/DS_/WF_ 前缀或显式 --type): " + key);
|
|
700
|
+
throw new Error("无法由 confKey 推导 confType(需 CONN_/DS_/WF_/API_ 前缀或显式 --type): " + key);
|
|
701
|
+
}
|
|
702
|
+
// confGroup 仅在 confType=api 或调用方显式指定时下发(见上方说明)
|
|
703
|
+
function confUseGroup(ctype, group) {
|
|
704
|
+
return ctype === "api" || (group !== undefined && group !== null && group !== "");
|
|
705
|
+
}
|
|
706
|
+
function confPutGroup(body, ctype, group) {
|
|
707
|
+
if (confUseGroup(ctype, group)) body.confGroup = group === undefined || group === null ? "" : String(group);
|
|
708
|
+
return body;
|
|
709
|
+
}
|
|
710
|
+
function confGroupSuffix(ctype, group) { // GET/DELETE 用的 query 后缀
|
|
711
|
+
return confUseGroup(ctype, group) ? "&confGroup=" + encodeURIComponent(group === undefined || group === null ? "" : String(group)) : "";
|
|
712
|
+
}
|
|
713
|
+
function confItemId(it) { // 同 confKey 可分布在多个 confGroup,带分组时用「组|键」做映射键,避免互相覆盖
|
|
714
|
+
const g = it && it.confGroup;
|
|
715
|
+
return g ? g + "|" + (it.confKey || "") : ((it && it.confKey) || "");
|
|
716
|
+
}
|
|
717
|
+
function confLookup(map, it) { // 兼容后端回包是否带 confGroup:先按「组|键」取,再退化到 confKey
|
|
718
|
+
return map[confItemId(it)] || map[it.confKey];
|
|
571
719
|
}
|
|
572
720
|
function confFail(j, url) {
|
|
573
721
|
const m = (j && (j.msg || (j.actionMsg && j.actionMsg.message))) || "";
|
|
@@ -592,51 +740,62 @@ async function confRequest(method, url, bodyObj) {
|
|
|
592
740
|
checkAuthFail(resp, text, j);
|
|
593
741
|
return j;
|
|
594
742
|
}
|
|
595
|
-
async function confGetData(ctype, key, silent) {
|
|
596
|
-
const url = CONF_GET_PATH + "?confType=" + encodeURIComponent(ctype) + "&confKey=" + encodeURIComponent(key);
|
|
743
|
+
async function confGetData(ctype, key, silent, group) {
|
|
744
|
+
const url = CONF_GET_PATH + "?confType=" + encodeURIComponent(ctype) + "&confKey=" + encodeURIComponent(key) + confGroupSuffix(ctype, group);
|
|
597
745
|
let r;
|
|
598
746
|
try { r = await confRequest("GET", url); }
|
|
599
747
|
catch (e) { if (silent) return null; throw e; }
|
|
600
748
|
const data = (r && r.data) || null;
|
|
601
749
|
if (!silent && (!data || data.confValue === undefined || data.confValue === null || data.confValue === "")) {
|
|
602
|
-
throw new Error("配置不存在或为空: confType=" + ctype + " confKey=" + key);
|
|
750
|
+
throw new Error("配置不存在或为空: confType=" + ctype + (group ? " confGroup=" + group : "") + " confKey=" + key);
|
|
603
751
|
}
|
|
604
752
|
return data;
|
|
605
753
|
}
|
|
606
754
|
function confNormalizeBatch(payload) {
|
|
607
755
|
const data = (payload && payload.data) || null;
|
|
608
756
|
const out = {};
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
}
|
|
615
|
-
}
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
out[k] = v;
|
|
619
|
-
}
|
|
620
|
-
}
|
|
757
|
+
const put = (row, fallbackKey) => {
|
|
758
|
+
if (!row || typeof row !== "object") return;
|
|
759
|
+
const k = row.confKey || row.conf_key || fallbackKey;
|
|
760
|
+
if (!k) return;
|
|
761
|
+
if (row.confGroup === undefined) { const g = row.conf_group; if (g !== undefined && g !== null && g !== "") row.confGroup = g; }
|
|
762
|
+
out[confItemId({ confGroup: row.confGroup, confKey: k })] = row;
|
|
763
|
+
};
|
|
764
|
+
if (Array.isArray(data)) for (const row of data) put(row);
|
|
765
|
+
else if (data && typeof data === "object") for (const [k, v] of Object.entries(data)) put(v, k);
|
|
621
766
|
return out;
|
|
622
767
|
}
|
|
768
|
+
// 按类型+分组批量取回(batchGetChatBiConfigMapByTypeAndGroup),payload = [{confType, confGroup}...]
|
|
769
|
+
async function confBatchByTypeAndGroup(pairs) {
|
|
770
|
+
const payload = pairs.map((p) => ({ confType: p.confType, confGroup: p.confGroup === undefined || p.confGroup === null ? "" : String(p.confGroup) }));
|
|
771
|
+
const r = await confRequest("POST", CONF_BATCH_GROUP_PATH, payload);
|
|
772
|
+
return confNormalizeBatch(r);
|
|
773
|
+
}
|
|
623
774
|
async function cmdConfGet(args, json, keys) {
|
|
624
775
|
const key = keys[0];
|
|
625
|
-
if (!key) throw new Error("conf get 缺少 confKey(用法: conf get <key> [--type t])");
|
|
776
|
+
if (!key) throw new Error("conf get 缺少 confKey(用法: conf get <key> [--type t] [--group g])");
|
|
626
777
|
const ctype = confKeyType(key, args.type);
|
|
627
|
-
|
|
778
|
+
let data;
|
|
779
|
+
try {
|
|
780
|
+
data = await confGetData(ctype, key, false, args.group);
|
|
781
|
+
} catch (e) {
|
|
782
|
+
if (ctype === "api" && !args.group) {
|
|
783
|
+
throw new Error(e.message + "(confType=api 建议用 --group 指定分组,如 --group workflow;或用 conf group api <分组> 查看该分组下全部配置)");
|
|
784
|
+
}
|
|
785
|
+
throw e;
|
|
786
|
+
}
|
|
628
787
|
if (json) return data;
|
|
629
788
|
console.log(JSON.stringify(data, null, 1));
|
|
630
789
|
}
|
|
631
790
|
async function cmdConfBatch(args, json, keys) {
|
|
632
|
-
if (!keys.length) throw new Error("conf batch 缺少 confKey 列表(用法: conf batch <key1> [key2 ...])");
|
|
633
|
-
const items = keys.map((k) =>
|
|
791
|
+
if (!keys.length) throw new Error("conf batch 缺少 confKey 列表(用法: conf batch <key1> [key2 ...] [--type t] [--group g])");
|
|
792
|
+
const items = keys.map((k) => { const t = confKeyType(k, args.type); return confPutGroup({ confType: t, confKey: k }, t, args.group); });
|
|
634
793
|
let map = {};
|
|
635
794
|
try {
|
|
636
795
|
const r = await confRequest("POST", CONF_BATCH_PATH, items);
|
|
637
796
|
map = confNormalizeBatch(r);
|
|
638
797
|
for (const it of items) {
|
|
639
|
-
const c = map
|
|
798
|
+
const c = confLookup(map, it);
|
|
640
799
|
if (!c || c.confValue === undefined || c.confValue === null || c.confValue === "") {
|
|
641
800
|
throw new Error("批量响应缺失 confKey=" + it.confKey);
|
|
642
801
|
}
|
|
@@ -645,43 +804,82 @@ async function cmdConfBatch(args, json, keys) {
|
|
|
645
804
|
if (String(e.message).indexOf("批量响应缺失") === 0) throw e;
|
|
646
805
|
console.error("[config-resolve] 批量接口不可用,降级逐条单查: " + e.message);
|
|
647
806
|
map = {};
|
|
648
|
-
for (const it of items) map[it
|
|
807
|
+
for (const it of items) map[confItemId(it)] = await confGetData(it.confType, it.confKey, false, it.confGroup);
|
|
649
808
|
}
|
|
650
809
|
if (json) return map;
|
|
651
|
-
printHuman("配置 " + items.length + " 条", keys.map((k) =>
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
810
|
+
printHuman("配置 " + items.length + " 条", keys.map((k) => {
|
|
811
|
+
const it = items.find((x) => x.confKey === k);
|
|
812
|
+
const c = confLookup(map, it) || {};
|
|
813
|
+
return { key: k, confType: it.confType, confGroup: it.confGroup ?? "", confValue: c.confValue ?? "", confName: c.confName ?? "" };
|
|
814
|
+
}), ["key", "confType", "confGroup", "confValue", "confName"]);
|
|
815
|
+
}
|
|
816
|
+
async function cmdConfBatchFile(args, json, keys) {
|
|
817
|
+
const val = flagOrPos(args, "batch-file", keys);
|
|
818
|
+
if (val === undefined) throw new Error("conf batch-file 缺少参数(用法: conf batch-file @items.json)");
|
|
819
|
+
const items = readJsonArg(String(val), "--batch-file");
|
|
656
820
|
const list = Array.isArray(items) ? items : [items];
|
|
657
821
|
const map = {};
|
|
822
|
+
const meta = {};
|
|
658
823
|
for (const it of list) {
|
|
659
824
|
if (!it.confKey || !it.confType) continue;
|
|
660
|
-
|
|
825
|
+
const id = confItemId(it);
|
|
826
|
+
map[id] = await confGetData(it.confType, it.confKey, false, it.confGroup);
|
|
827
|
+
meta[id] = it;
|
|
661
828
|
}
|
|
662
829
|
if (json) return map;
|
|
663
|
-
printHuman("配置 " + Object.keys(map).length + " 条", Object.keys(map).map((
|
|
830
|
+
printHuman("配置 " + Object.keys(map).length + " 条", Object.keys(map).map((id) => ({ key: meta[id].confKey, confGroup: meta[id].confGroup ?? "", confValue: (map[id] || {}).confValue ?? "", confName: (map[id] || {}).confName ?? "" })), ["key", "confGroup", "confValue", "confName"]);
|
|
831
|
+
}
|
|
832
|
+
// 按类型+分组批量查(batchGetChatBiConfigMapByTypeAndGroup):pairs = [{confType, confGroup}...]
|
|
833
|
+
async function cmdConfGroup(args, json, keys) {
|
|
834
|
+
const ctype = keys[0] || args.type;
|
|
835
|
+
const group = keys[1] !== undefined ? keys[1] : args.group;
|
|
836
|
+
if (!ctype) throw new Error("conf group 缺少 confType(用法: conf group <confType> <confGroup> | --type api --group workflow)");
|
|
837
|
+
if (group === undefined) throw new Error("conf group 缺少 confGroup(用法: conf group <confType> <confGroup>)");
|
|
838
|
+
const map = await confBatchByTypeAndGroup([{ confType: ctype, confGroup: group }]);
|
|
839
|
+
const rows = Object.values(map);
|
|
840
|
+
const onlyKey = args.key;
|
|
841
|
+
const filtered = onlyKey ? rows.filter((r) => (r.confKey || r.conf_key) === onlyKey) : rows;
|
|
842
|
+
if (json) return onlyKey ? (filtered[0] ?? null) : filtered;
|
|
843
|
+
if (onlyKey && !filtered.length) throw new Error("配置不存在: confType=" + ctype + " confGroup=" + group + " confKey=" + onlyKey);
|
|
844
|
+
printHuman("confType=" + ctype + " confGroup=" + group + " 配置 " + filtered.length + " 条",
|
|
845
|
+
filtered.map((r) => ({ confKey: r.confKey || r.conf_key || "", confValue: r.confValue ?? "", confName: r.confName ?? "", confAttrs: r.confAttrs ?? "" })),
|
|
846
|
+
["confKey", "confValue", "confName", "confAttrs"]);
|
|
847
|
+
return filtered;
|
|
848
|
+
}
|
|
849
|
+
async function cmdConfGroupFile(args, json, keys) {
|
|
850
|
+
const val = flagOrPos(args, "group-file", keys);
|
|
851
|
+
if (val === undefined) throw new Error("conf group-file 缺少参数(用法: conf group-file @items.json,形如 [{\"confType\":\"api\",\"confGroup\":\"workflow\"}])");
|
|
852
|
+
const pairs = readJsonArg(String(val), "--group-file");
|
|
853
|
+
const list = (Array.isArray(pairs) ? pairs : [pairs]).filter((p) => p && p.confType);
|
|
854
|
+
if (!list.length) throw new Error("conf group-file 内容为空或缺少 confType");
|
|
855
|
+
const map = await confBatchByTypeAndGroup(list);
|
|
856
|
+
const rows = Object.values(map);
|
|
857
|
+
if (json) return map;
|
|
858
|
+
printHuman("批量分组配置 " + rows.length + " 条",
|
|
859
|
+
rows.map((r) => ({ confType: r.confType ?? "", confGroup: r.confGroup ?? "", confKey: r.confKey || r.conf_key || "", confValue: r.confValue ?? "", confName: r.confName ?? "" })),
|
|
860
|
+
["confType", "confGroup", "confKey", "confValue", "confName"]);
|
|
861
|
+
return map;
|
|
664
862
|
}
|
|
665
|
-
async function confResolveAll(keys) {
|
|
863
|
+
async function confResolveAll(keys, group) {
|
|
666
864
|
// 多占位符:先批量预取一轮(失败降级逐条单查),返回 {key: data}
|
|
667
|
-
const items = keys.map((k) => ({ confType: confKeyType(k, ""), confKey: k }));
|
|
865
|
+
const items = keys.map((k) => confPutGroup({ confType: confKeyType(k, ""), confKey: k }, confKeyType(k, ""), group));
|
|
668
866
|
const map = {};
|
|
669
867
|
try {
|
|
670
868
|
const r = await confRequest("POST", CONF_BATCH_PATH, items);
|
|
671
869
|
const got = confNormalizeBatch(r);
|
|
672
870
|
for (const it of items) {
|
|
673
|
-
const c = got
|
|
871
|
+
const c = confLookup(got, it);
|
|
674
872
|
if (!c || c.confValue === undefined || c.confValue === null || c.confValue === "") throw new Error("缺失 " + it.confKey);
|
|
675
873
|
map[it.confKey] = c;
|
|
676
874
|
}
|
|
677
875
|
} catch {
|
|
678
|
-
for (const it of items) map[it.confKey] = await confGetData(it.confType, it.confKey);
|
|
876
|
+
for (const it of items) map[it.confKey] = await confGetData(it.confType, it.confKey, false, it.confGroup);
|
|
679
877
|
}
|
|
680
878
|
return map;
|
|
681
879
|
}
|
|
682
|
-
async function confResolveText(text, json) {
|
|
880
|
+
async function confResolveText(text, json, group) {
|
|
683
881
|
const found = [...new Set([...text.matchAll(CONF_TOKEN_RE)].map((m) => m[1]))];
|
|
684
|
-
const map = found.length ? await confResolveAll(found) : {};
|
|
882
|
+
const map = found.length ? await confResolveAll(found, group) : {};
|
|
685
883
|
const out = text.replace(CONF_TOKEN_RE, (m0, k) => {
|
|
686
884
|
const cfg = map[k];
|
|
687
885
|
if (!cfg) throw new Error("配置不存在或为空: " + k);
|
|
@@ -692,45 +890,47 @@ async function confResolveText(text, json) {
|
|
|
692
890
|
}
|
|
693
891
|
async function cmdConfResolve(args, json, keys) {
|
|
694
892
|
let text = keys[0];
|
|
695
|
-
if (!text) throw new Error("conf resolve 缺少文本(用法: conf resolve '<含#{KEY}文本>' | @file.txt)");
|
|
893
|
+
if (!text) throw new Error("conf resolve 缺少文本(用法: conf resolve '<含#{KEY}文本>' | @file.txt [--group g])");
|
|
696
894
|
if (text.startsWith("@")) text = fs.readFileSync(text.slice(1), "utf-8");
|
|
697
|
-
const out = await confResolveText(text, json);
|
|
895
|
+
const out = await confResolveText(text, json, args.group);
|
|
698
896
|
if (json) { console.log(JSON.stringify(out, null, 1)); return out; }
|
|
699
897
|
process.stdout.write(out + "\n");
|
|
700
898
|
}
|
|
701
|
-
async function cmdConfResolveFile(args, json) {
|
|
702
|
-
const p = args
|
|
703
|
-
if (!p) throw new Error("conf resolve-file 缺少文件路径(用法: conf resolve-file
|
|
899
|
+
async function cmdConfResolveFile(args, json, keys) {
|
|
900
|
+
const p = flagOrPos(args, "resolve-file", keys);
|
|
901
|
+
if (!p) throw new Error("conf resolve-file 缺少文件路径(用法: conf resolve-file <文件路径> [--group g])");
|
|
704
902
|
if (!fs.existsSync(p)) throw new Error("文件不存在: " + p);
|
|
705
903
|
const text = fs.readFileSync(p, "utf-8");
|
|
706
|
-
const out = await confResolveText(text, json);
|
|
904
|
+
const out = await confResolveText(text, json, args.group);
|
|
707
905
|
if (json) { console.log(JSON.stringify(out, null, 1)); return out; }
|
|
708
906
|
process.stdout.write(out + "\n");
|
|
709
907
|
}
|
|
710
908
|
async function cmdConfAdd(args, json, keys) {
|
|
711
909
|
const key = keys[0];
|
|
712
|
-
if (!key || args.value === undefined) throw new Error("conf add 缺少 confKey/confValue(用法: conf add <key> --value <真实值> [--name] [--attrs] [--type] [--force])");
|
|
910
|
+
if (!key || args.value === undefined) throw new Error("conf add 缺少 confKey/confValue(用法: conf add <key> --value <真实值> [--name] [--attrs] [--type] [--group] [--force])");
|
|
713
911
|
const ctype = confKeyType(key, args.type);
|
|
714
|
-
if (!args.force && await confGetData(ctype, key, true)) {
|
|
912
|
+
if (!args.force && await confGetData(ctype, key, true, args.group)) {
|
|
715
913
|
console.error("[config-resolve] 配置已存在,跳过新增(如需修改请用 conf update;确需重建可先 conf delete): " + key);
|
|
716
914
|
return null;
|
|
717
915
|
}
|
|
718
|
-
const item = { confType: ctype, confKey: key, confValue: String(args.value), confAttrs: args.attrs || "", confName: args.name || "" };
|
|
916
|
+
const item = confPutGroup({ confType: ctype, confKey: key, confValue: String(args.value), confAttrs: args.attrs || "", confName: args.name || "" }, ctype, args.group);
|
|
719
917
|
const r = await confRequest("POST", CONF_ADD_PATH, [item]);
|
|
720
918
|
if (json) return r;
|
|
721
|
-
console.log("新增成功: " + key + " = " + item.confValue);
|
|
919
|
+
console.log("新增成功: " + (item.confGroup ? item.confGroup + "/" : "") + key + " = " + item.confValue);
|
|
722
920
|
}
|
|
723
|
-
async function cmdConfAddFile(args, json) {
|
|
724
|
-
|
|
725
|
-
|
|
921
|
+
async function cmdConfAddFile(args, json, keys) {
|
|
922
|
+
const val = flagOrPos(args, "add-file", keys);
|
|
923
|
+
if (val === undefined) throw new Error("conf add-file 缺少参数(用法: conf add-file @items.json,元素形如 {confType,confGroup,confKey,confValue,confName,confAttrs})");
|
|
924
|
+
const items = readJsonArg(String(val), "--add-file");
|
|
726
925
|
const list = Array.isArray(items) ? items : [items];
|
|
727
926
|
const toAdd = [];
|
|
728
927
|
for (const it of list) {
|
|
729
928
|
if (!it.confKey || it.confValue === undefined) continue;
|
|
730
929
|
const ctype = it.confType || confKeyType(it.confKey, args.type);
|
|
731
|
-
const
|
|
930
|
+
const group = it.confGroup !== undefined ? it.confGroup : args.group;
|
|
931
|
+
const dup = await confGetData(ctype, it.confKey, true, group);
|
|
732
932
|
if (dup) { console.error("[config-resolve] 已存在跳过: " + it.confKey); continue; }
|
|
733
|
-
toAdd.push({ confType: ctype, confKey: it.confKey, confValue: String(it.confValue), confAttrs: it.confAttrs || "", confName: it.confName || "" });
|
|
933
|
+
toAdd.push(confPutGroup({ confType: ctype, confKey: it.confKey, confValue: String(it.confValue), confAttrs: it.confAttrs || "", confName: it.confName || "" }, ctype, group));
|
|
734
934
|
}
|
|
735
935
|
if (!toAdd.length) { console.log("全部已存在,无新增"); return null; }
|
|
736
936
|
const r = await confRequest("POST", CONF_ADD_PATH, toAdd);
|
|
@@ -739,37 +939,40 @@ async function cmdConfAddFile(args, json) {
|
|
|
739
939
|
}
|
|
740
940
|
async function cmdConfUpdate(args, json, keys) {
|
|
741
941
|
const key = keys[0];
|
|
742
|
-
if (!key) throw new Error("conf update 缺少 confKey(用法: conf update <key> [--value v] [--name n] [--attrs a] [--type t])");
|
|
942
|
+
if (!key) throw new Error("conf update 缺少 confKey(用法: conf update <key> [--value v] [--name n] [--attrs a] [--type t] [--group g])");
|
|
743
943
|
if (args.value === undefined && args.name === undefined && args.attrs === undefined) {
|
|
744
944
|
throw new Error("conf update 至少需提供 --value / --name / --attrs 之一");
|
|
745
945
|
}
|
|
746
|
-
const
|
|
946
|
+
const ctype = confKeyType(key, args.type);
|
|
947
|
+
const body = confPutGroup({ confType: ctype, confKey: key }, ctype, args.group);
|
|
747
948
|
if (args.value !== undefined) body.confValue = String(args.value);
|
|
748
949
|
if (args.name !== undefined) body.confName = args.name;
|
|
749
950
|
if (args.attrs !== undefined) body.confAttrs = args.attrs;
|
|
750
951
|
const r = await confRequest("POST", CONF_UPDATE_PATH, body);
|
|
751
952
|
if (json) return r;
|
|
752
|
-
console.log("更新成功: " + key);
|
|
953
|
+
console.log("更新成功: " + (body.confGroup ? body.confGroup + "/" : "") + key);
|
|
753
954
|
}
|
|
754
955
|
async function cmdConfDelete(args, json, keys) {
|
|
755
956
|
const key = keys[0];
|
|
756
|
-
if (!key) throw new Error("conf delete 缺少 confKey(用法: conf delete <key> [--type t])");
|
|
957
|
+
if (!key) throw new Error("conf delete 缺少 confKey(用法: conf delete <key> [--type t] [--group g])");
|
|
757
958
|
const ctype = confKeyType(key, args.type);
|
|
758
|
-
const url = CONF_DELETE_PATH + "?confType=" + encodeURIComponent(ctype) + "&confKey=" + encodeURIComponent(key);
|
|
959
|
+
const url = CONF_DELETE_PATH + "?confType=" + encodeURIComponent(ctype) + "&confKey=" + encodeURIComponent(key) + confGroupSuffix(ctype, args.group);
|
|
759
960
|
const r = await confRequest("DELETE", url);
|
|
760
961
|
if (json) return r;
|
|
761
|
-
console.log("删除成功: " + key);
|
|
962
|
+
console.log("删除成功: " + (confUseGroup(ctype, args.group) && args.group ? args.group + "/" : "") + key);
|
|
762
963
|
}
|
|
763
964
|
async function cmdConf(sub, args, json, pos) {
|
|
764
965
|
const keys = pos.slice(2);
|
|
765
966
|
switch (sub) {
|
|
766
967
|
case "get": return cmdConfGet(args, json, keys);
|
|
767
968
|
case "batch": return cmdConfBatch(args, json, keys);
|
|
768
|
-
case "batch-file": return cmdConfBatchFile(args, json);
|
|
969
|
+
case "batch-file": return cmdConfBatchFile(args, json, keys);
|
|
970
|
+
case "group": return cmdConfGroup(args, json, keys);
|
|
971
|
+
case "group-file": return cmdConfGroupFile(args, json, keys);
|
|
769
972
|
case "resolve": return cmdConfResolve(args, json, keys);
|
|
770
|
-
case "resolve-file": return cmdConfResolveFile(args, json);
|
|
973
|
+
case "resolve-file": return cmdConfResolveFile(args, json, keys);
|
|
771
974
|
case "add": return cmdConfAdd(args, json, keys);
|
|
772
|
-
case "add-file": return cmdConfAddFile(args, json);
|
|
975
|
+
case "add-file": return cmdConfAddFile(args, json, keys);
|
|
773
976
|
case "update": return cmdConfUpdate(args, json, keys);
|
|
774
977
|
case "delete": return cmdConfDelete(args, json, keys);
|
|
775
978
|
default: usage(); process.exit(1);
|
|
@@ -1168,16 +1371,21 @@ noun 词库查询
|
|
|
1168
1371
|
field 关联字段翻译
|
|
1169
1372
|
field id2name --module <m> --type <t> --ids id1,id2 [--form-param '{"fieldId":"..","module":"ebuilderform"}'|--eb-browser-param '{..}'|--property-type x]
|
|
1170
1373
|
field name2id --module <m> --type <t> --name 名称 [同上附加参数]
|
|
1171
|
-
conf 环境参数配置中心(chatBi config map,替代 config-resolve 脚本 URL 直连;confType 默认由 CONN_/DS_/WF_ 前缀推导,可 --type 显式)
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
conf
|
|
1175
|
-
conf
|
|
1176
|
-
conf
|
|
1177
|
-
conf
|
|
1178
|
-
conf
|
|
1179
|
-
conf
|
|
1180
|
-
conf
|
|
1374
|
+
conf 环境参数配置中心(chatBi config map,替代 config-resolve 脚本 URL 直连;confType 默认由 CONN_/DS_/WF_/API_ 前缀推导,可 --type 显式)
|
|
1375
|
+
confGroup(配置分组,v0.1.9 新增,可选):confType=api 时后端支持二级分层(如 api/workflow),
|
|
1376
|
+
定位维度 = confType+confGroup+confKey;仅 confType=api(或显式传 --group)时才下发 confGroup,其他类型不带该字段
|
|
1377
|
+
conf get <key> [--type t] [--group g] # 单查(confValue/confAttrs/confName 全量 JSON)
|
|
1378
|
+
conf batch <key1> [key2 ...] [--type t] [--group g] # 批量查(批量接口异常自动降级逐条单查)
|
|
1379
|
+
conf batch-file @items.json # 按 [{confType,confGroup,confKey}..] 显式批量查(支持任意 confType)
|
|
1380
|
+
conf group <confType> <confGroup> [--key k] # 按类型+分组批量查(batchGetChatBiConfigMapByTypeAndGroup)
|
|
1381
|
+
conf group-file @items.json # 按 [{"confType":"api","confGroup":"workflow"}..] 多分组批量查
|
|
1382
|
+
conf resolve '<含#{KEY}文本>'|@file.txt [--group g] # 文本占位符替换为真实值(含中文建议 @file)
|
|
1383
|
+
conf resolve-file <文件路径> [--group g] # 读文件替换全部占位符并打印(不改写原文件)
|
|
1384
|
+
conf add <key> --value <真实值> [--name 说明] [--attrs '{"request":..,"response":..}'] [--type t] [--group g] [--force] # 新增(默认判重,已存在跳过)
|
|
1385
|
+
conf add-file @items.json # JSON 数组批量新增(逐条判重,元素可带 confGroup)
|
|
1386
|
+
conf update <key> [--value v] [--name n] [--attrs a] [--type t] [--group g] # 更新(confType+confGroup+confKey 定位,传哪个字段更哪个)
|
|
1387
|
+
conf delete <key> [--type t] [--group g] # 删除
|
|
1388
|
+
示例: conf group api workflow | conf add GET_WORKFLOW_LIST --type api --group workflow --value /api/workflow/getList --name 查询流程列表
|
|
1181
1389
|
exp 专家包版本管理(expert-repository/v1,响应以 data.actionMsg.code=0 判成功)
|
|
1182
1390
|
exp status --expert <code> [--branch main] # 仓库状态(headCommitId/headRevision=提交基线)
|
|
1183
1391
|
exp manifest --expert <code> [--commit <id>] # 文件清单(sha256)
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "e10-data",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "泛微 E10 数据接口统一 CLI(data-assistant 专家配套):DSL 查询/数据源/文档/词库/关联字段翻译/环境参数配置中心(conf)
|
|
3
|
+
"version": "0.1.10",
|
|
4
|
+
"description": "泛微 E10 数据接口统一 CLI(data-assistant 专家配套):DSL 查询/数据源/文档/词库/关联字段翻译/环境参数配置中心(conf,支持 confGroup 分组)/专家包版本管理;认证加解密全部交由 weaver-e10-login(本 CLI 不再自行解密,通过 e10-login 命令出口获取凭据)",
|
|
5
5
|
"author": "e10-dw",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"type": "module",
|