@xbongbong/xbbcli 2.4.1 → 2.4.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +36 -17
- package/auth-login.js +1 -1
- package/auth-logout.js +5 -6
- package/auth-refresh-cache.js +117 -0
- package/auth-status.js +55 -11
- package/package.json +1 -1
- package/token-del.js +50 -10
- package/token-list.js +40 -21
- package/token-use.js +38 -5
- package/xbb-config.js +5 -9
- package/xbb-token-store.js +91 -15
package/README.md
CHANGED
|
@@ -98,6 +98,20 @@ xbbcli auth-login --keepOpen # 登录成功后保留浏览器窗口(默
|
|
|
98
98
|
- `--debug` 输出请求体、返回体与浏览器信息(密钥只显示掩码),`--raw` 输出换取 API token 接口的原始响应
|
|
99
99
|
- 失败时返回合成错误行:`NO_BROWSER`、`BROWSER_CLOSED`、`LOGIN_TIMEOUT`、`API_TOKEN_FAILED`、`CORPID_MISMATCH`、`SAVE_FAILED` 等
|
|
100
100
|
|
|
101
|
+
### 刷新本地缓存(auth-refresh-cache)
|
|
102
|
+
|
|
103
|
+
`token-set` / `auth-login` 保存凭证时会顺带刷新三份本地缓存。插件升级(新增命令导致命令映射变化)、缓存过期或被删除后,可以单独重刷,不需要重新输入 token:
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
xbbcli auth-refresh-cache -f json
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
- 刷新范围(仅当前启用公司):`~/.xbbcli/<corpid>.formlist.json`(表单清单)、`~/.xbbcli/<corpid>.command-map.md`(命令映射)、`~/.xbbcli/<corpid>.department-user.json`(部门/员工清单),并把 `corpName` / `userName` 回填进 `config.env`
|
|
110
|
+
- 环境变量模式(`XBB_*`)下同样可用:只刷新缓存文件,不回填 `config.env`
|
|
111
|
+
- 只支持当前启用公司:底层接口一律使用启用公司的凭证,切换公司请先用 `xbbcli token-use`
|
|
112
|
+
- `--userId` 覆盖用于匹配 `userName` 的员工 id(默认取配置里的 `userId`);`--debug` 在 stderr 输出配置来源、corpid 与三份缓存文件路径
|
|
113
|
+
- 失败时返回 `status=error`、`code=CACHE_REFRESH_FAILED`,`message` 以「步骤 formlist / command-map / department-user / profile」指出中断位置,已完成的部分照常在对应列输出
|
|
114
|
+
|
|
101
115
|
`token-set` 会按 `corpid` 新增或覆盖一家公司,并把该公司置为 `enable=true`(其余公司自动置为 `false`),同时写入公司名称 `corpName`(取部门 `id` 为 `1` 的部门名称)和操作人姓名 `userName`(从员工列表中按 `userId` 匹配)。除 `token-set`、`token-list`、`token-use`、`token-del`、`auth-status`、`auth-logout` 外,其余命令都使用 `enable` 为 `true` 的公司配置。
|
|
102
116
|
|
|
103
117
|
同时会自动拉取两份表单清单并合并缓存到:
|
|
@@ -145,10 +159,10 @@ xbbcli user-list --pageSize 200 -f json
|
|
|
145
159
|
xbbcli auth-status # 回显当前激活的配置(token 默认掩码)
|
|
146
160
|
xbbcli auth-status --showToken # 需要完整 token 时
|
|
147
161
|
xbbcli auth-logout # 登出:删除激活的那一家配置 + 清除 XBB_* 环境变量
|
|
148
|
-
xbbcli auth-logout --
|
|
162
|
+
xbbcli auth-logout --env 0 # 只删 config.env,保留环境变量
|
|
149
163
|
```
|
|
150
164
|
|
|
151
|
-
- `auth-status` 以 `key` / `value` 两列回显当前生效的配置值,固定 6 行:`corpid`、`corpName`、`userName`、`userId`、`baseurl`、`token
|
|
165
|
+
- `auth-status` 以 `key` / `value` 两列回显当前生效的配置值,固定 6 行:`corpid`、`corpName`、`userName`、`userId`、`baseurl`、`token`;随后追加 1 行 `验证`(用一次 `user-list --pageSize 1` 调用实测 token:`有效` / `无效-请重新配置token`);不再输出 `status`、`source`、`message`、`enable`、`companyCount`、`envActive`、`configFile`、`code`、`msg` 这些元信息列。
|
|
152
166
|
|
|
153
167
|
```text
|
|
154
168
|
key value
|
|
@@ -158,15 +172,17 @@ userName 陈会兵
|
|
|
158
172
|
userId 02415643151585
|
|
159
173
|
baseurl https://proapi.xbongbong.com
|
|
160
174
|
token user_6***a7da
|
|
175
|
+
验证 有效
|
|
161
176
|
note 以上取值来自环境变量 XBB_*(config.env 中没有启用公司,回落到环境变量);环境变量模式不支持多公司切换
|
|
162
177
|
```
|
|
163
178
|
|
|
164
|
-
- 来源(`config.env` 启用公司 还是 `XBB_*`)与冲突提醒只在需要时以 `note` 行追加:环境变量与 `config.env`
|
|
165
|
-
-
|
|
179
|
+
- 来源(`config.env` 启用公司 还是 `XBB_*`)与冲突提醒只在需要时以 `note` 行追加:环境变量与 `config.env` 同时存在时提示「环境变量优先,`config.env` 中启用(`enable=true`)的公司被覆盖」并提示清除环境变量可切回配置文件;`config.env` 里出现多个 `enable=true` 时提示用 `token-use` 修正。
|
|
180
|
+
- `auth-status` 的 `验证` 行会实打实发起一次 `user-list --pageSize 1` 请求(等价于手工执行 `xbbcli user-list --pageSize 1`):接口成功(含 `NO_DATA` 空清单)→ `有效`;接口返回业务错误码或请求异常 → `无效-请重新配置token`。没有生效配置或缺少 token 时不再发起请求,直接给出 `无效-请重新配置token`;`--debug` 会输出判定结果与原因。
|
|
181
|
+
- 没有任何生效配置时,6 个配置值一律留空(`验证` 行为 `无效-请重新配置token`)并给出 `note` 提示下一步命令(即使 `XBB_BASEURL` / `XBB_USERID` 单独残留也不会当成生效值回显)。配置文件路径、公司数量、生效的环境变量名改用 `--debug` 输出到 stderr。
|
|
166
182
|
- `auth-status` 默认把 token 掩码为「前 6 + `***` + 后 4」(长度不足 11 时只保留前 2 位),只有 `--showToken` 才输出明文。
|
|
167
|
-
- `auth-logout` 只删除 `enable=true` 的那一条,**不会**把其他公司自动提升为启用:登出后本地可能一个启用公司都没有,需要 `xbbcli token-use --corpid <CORPID>` 或重新 `auth-login` / `token-set`。想保留其他公司并可切换时,用 `xbbcli token-del --corpid <CORPID
|
|
183
|
+
- `auth-logout` 只删除 `enable=true` 的那一条,**不会**把其他公司自动提升为启用:登出后本地可能一个启用公司都没有,需要 `xbbcli token-use --corpid <CORPID>` 或重新 `auth-login` / `token-set`。想保留其他公司并可切换时,用 `xbbcli token-del --corpid <CORPID>`(它会保证剩余配置仍有且仅有一个启用;默认还会删除 `XBB_*` 环境变量,传 `--env 0` 可保留)。
|
|
168
184
|
- 删除前 `config.env` 会整份备份到 `~/.xbbcli/config.env.bak`(每次登出覆盖),`backupFile` 列给出路径;误删把 `.bak` 复制回 `config.env` 即可恢复。
|
|
169
|
-
- 默认**同时**清除 `XBB_*` 环境变量:Windows 用 `reg delete HKCU\Environment` 删除用户级变量(已打开的终端要重开才生效,`envStored` 为 `user`,原本就不存在时为 `none`);macOS/Linux 清理 `~/.xbbcli/env.sh` 里的 `export XBB_*` 行(文件清空则删除,`envStored` 为 `cleared` / `removed` / `none
|
|
185
|
+
- 默认**同时**清除 `XBB_*` 环境变量:Windows 用 `reg delete HKCU\Environment` 删除用户级变量(已打开的终端要重开才生效,`envStored` 为 `user`,原本就不存在时为 `none`);macOS/Linux 清理 `~/.xbbcli/env.sh` 里的 `export XBB_*` 行(文件清空则删除,`envStored` 为 `cleared` / `removed` / `none`)。传 `--env 0` 时只删 `config.env`,此时若环境变量仍在,`message` 会提示「其优先级高于 config.env,业务命令仍会使用它,本次不算彻底登出」(`XBB_ENV_ONLY=1` 时提示为「已强制只用环境变量」)。
|
|
170
186
|
- `config.env` 没有启用公司时:只有在环境变量确实存在时才只清环境变量(`config.env` 保持不动);两者都不存在则返回 `NO_ACTIVE_CONFIG`,不写任何文件。
|
|
171
187
|
- 任何情况都不改动 `<corpid>.formlist.json` / `<corpid>.command-map.md` / `<corpid>.department-user.json` 三份缓存,重新启用该公司时无需再次拉取。
|
|
172
188
|
|
|
@@ -185,30 +201,33 @@ note 以上取值来自环境变量 XBB_*(config.env 中没有启用公
|
|
|
185
201
|
|
|
186
202
|
读取优先级:
|
|
187
203
|
|
|
188
|
-
1. `config.env`
|
|
189
|
-
2. `config.env`
|
|
204
|
+
1. 环境变量 `XBB_*` 中 `XBB_CORPID` / `XBB_TOKEN` 有值时,优先使用环境变量(`config.env` 中 `enable=true` 的公司被覆盖,命令会以环境变量为准)
|
|
205
|
+
2. 环境变量不存在时,使用 `config.env` 中存在 `enable=true` 的公司(多公司切换行为不变)
|
|
190
206
|
3. 设置 `XBB_ENV_ONLY=1` 可强制只使用环境变量(完全忽略 `config.env`)
|
|
191
207
|
|
|
208
|
+
> 注意:由于环境变量优先,早先 `--env 1` 写入的 `XBB_*` 会覆盖之后 `token-set --env 0` 写入的 `config.env`(`token-set` 的 `message` 会给出提示)。要切回 `config.env` 请先清除 `XBB_*` 环境变量,或执行 `xbbcli auth-logout`(默认会一并清除)。
|
|
209
|
+
|
|
192
210
|
`token-set` / `auth-login` 通过 `--env` 控制存储方式:`--env 0`(默认)仅写入本地文件(`config.env` 与各类缓存),`--env 1` 仅写入环境变量(不写任何本地文件)。选 `--env 1` 时会把 `corpid`/`token`/`baseurl`/`userId` 写入环境变量:
|
|
193
211
|
|
|
194
212
|
- Windows:用 `setx` 写入用户级环境变量,**只对新开的终端生效**
|
|
195
213
|
- macOS/Linux:写入 `~/.xbbcli/env.sh`(`export` 形式,权限 600),需要 `source ~/.xbbcli/env.sh` 才在当前 shell 生效
|
|
196
214
|
|
|
197
|
-
环境变量模式只支持一家公司:此时 `token-list`
|
|
215
|
+
环境变量模式只支持一家公司:此时 `token-list` 会把环境变量行(`source=env`)与 `config.env` 中的公司行(`source=config`)一起列出,并在 `msg` 列标注各自当前是否生效;`token-use` 会在切换 `config.env` 启用公司的同时,把该公司凭证同步写回 `XBB_*` 环境变量(目标公司缺 `token` 时拒绝切换,避免残留旧 `XBB_TOKEN`);`token-del` 默认在删除公司的同时一并删除 `XBB_*` 环境变量(`--env 0` 可保留),并在 `message` 说明删除后的实际生效来源。
|
|
198
216
|
|
|
199
|
-
> 注意:`token-del`
|
|
217
|
+
> 注意:`token-del --env 0` 保留环境变量时,若 `XBB_*` 仍有效,其优先级高于 `config.env`,业务命令仍会使用它(`message` 会给出提示);要连环境变量一起清理,用 `token-del` 默认行为(不传 `--env`)或执行 `xbbcli auth-logout`(默认就会清除 `XBB_*`);也可以手工执行 `setx XBB_TOKEN ""`(其余 `XBB_*` 同理,Windows 需重开终端)或删除 `~/.xbbcli/env.sh` 并 `unset` 当前 shell 中已导出的变量。
|
|
200
218
|
|
|
201
219
|
## 当前支持的命令
|
|
202
220
|
|
|
203
221
|
### 配置
|
|
204
222
|
|
|
205
223
|
- `auth-login`:拉起本地浏览器完成交互式登录(账号密码 / 短信 / 扫码均可),自动读取 Web 会话、换取 API token 并复用 `token-set` 落盘;支持 `--browser`、`--timeout`、`--corpid`、`--keepOpen`、`--env`、`--debug`、`--raw`
|
|
206
|
-
- `auth-status`:以 `key` / `value` 两列回显当前激活配置的 `corpid`、`corpName`、`userName`、`userId`、`baseurl`、`token`(token 默认中间掩码,`--showToken`
|
|
207
|
-
- `auth-logout`:删除 `config.env` 中 `enable=true` 的那一条配置,删除前整份备份为 `~/.xbbcli/config.env.bak`;不自动把其他公司提升为启用;默认同时清除 `XBB_*` 环境变量(`--
|
|
224
|
+
- `auth-status`:以 `key` / `value` 两列回显当前激活配置的 `corpid`、`corpName`、`userName`、`userId`、`baseurl`、`token`(token 默认中间掩码,`--showToken` 出明文),并追加 `验证` 行(调用一次 `user-list --pageSize 1` 实测 token,`有效` / `无效-请重新配置token`);来源与冲突提醒以 `note` 行追加,无生效配置时六个值留空并给出 `note`;`--debug` 在 stderr 输出配置文件路径、公司数量、生效的环境变量名与验证结果
|
|
225
|
+
- `auth-logout`:删除 `config.env` 中 `enable=true` 的那一条配置,删除前整份备份为 `~/.xbbcli/config.env.bak`;不自动把其他公司提升为启用;默认同时清除 `XBB_*` 环境变量(`--env 0` 跳过,返回 `envStored` / `envFile`);既无启用公司又无环境变量时返回 `NO_ACTIVE_CONFIG` 且不改动文件
|
|
226
|
+
- `auth-refresh-cache`:对当前启用公司重新拉取表单缓存、命令映射与部门/员工缓存,并回填 `corpName` / `userName`;失败时返回 `code=CACHE_REFRESH_FAILED`,`message` 注明中断步骤(`formlist` / `command-map` / `department-user` / `profile`),已完成的部分照常输出;支持 `--userId`、`--debug`
|
|
208
227
|
- `token-set`:保存个人 token、`corpid`、`userId`、`baseurl`,并刷新本地表单缓存、命令映射文件与部门/员工缓存;传入的 token 不以 `user_` 开头时,会先为该 `userId` 刷新并保存个人 token;`--env` 控制存储方式:`0`(默认)仅写入本地文件,`1` 仅写入 `XBB_*` 环境变量(Windows 用 `setx`,其他平台写 `~/.xbbcli/env.sh`)且不写任何本地文件;返回 `envStored` / `envFile` 两列
|
|
209
|
-
- `token-list
|
|
210
|
-
- `token-use`:切换当前启用的公司(`xbbcli token-use --corpid <CORPID
|
|
211
|
-
- `token-del`:删除指定公司的本地配置(`xbbcli token-del --corpid <CORPID
|
|
228
|
+
- `token-list`:同时列出环境变量行(`source=env`,仅 `XBB_CORPID` / `XBB_TOKEN` 有值时出现)与 `config.env` 中保存的全部公司(`source=config`,含 `corpName` 公司名称与 `userName` 操作人姓名),`msg` 列标注各行当前状态(当前生效 / 被环境变量覆盖 / 被 `XBB_ENV_ONLY=1` 忽略);`--showToken` 显示完整 token(默认脱敏)
|
|
229
|
+
- `token-use`:切换当前启用的公司(`xbbcli token-use --corpid <CORPID>`),保证仅且只有一个公司被启用;若环境变量 `XBB_*` 有值,同时把该公司凭证(`corpid`/`token`/`baseurl`/`userId`/`corpName`/`userName`,缺 `baseurl` 时按 `corpid` 推导)同步写回环境变量——Windows 用 `setx`(需重开终端)、macOS/Linux 写 `~/.xbbcli/env.sh`(需重新 `source`);同步失败时返回 `status=partial`,目标公司缺 `token` 时返回错误且不改动任何配置
|
|
230
|
+
- `token-del`:删除指定公司的本地配置(`xbbcli token-del --corpid <CORPID>`),并保证剩余配置中仅且只有一个公司被启用;默认同时删除 `XBB_*` 环境变量(`--env 0` 保留,返回 `envStored` / `envFile` 两列)
|
|
212
231
|
- `token-generate`:生成/获取个人 token,`--resetToken 0` 获取(默认)、`1` 刷新;`--checkUserId` 未传则用配置中的 `userId`
|
|
213
232
|
|
|
214
233
|
### 组织与人员
|
|
@@ -452,10 +471,10 @@ note 以上取值来自环境变量 XBB_*(config.env 中没有启用公
|
|
|
452
471
|
## 通用行为
|
|
453
472
|
|
|
454
473
|
- 先执行一次 `token-set`(或 `auth-login`)保存有效的 `corpid`、`token`、`userId`
|
|
455
|
-
- 用 `xbbcli auth-status` 查看当前生效的配置来自 `config.env` 还是环境变量;`xbbcli auth-logout` 删除激活配置并默认清除 `XBB_*` 环境变量(`--
|
|
474
|
+
- 用 `xbbcli auth-status` 查看当前生效的配置来自 `config.env` 还是环境变量;`xbbcli auth-logout` 删除激活配置并默认清除 `XBB_*` 环境变量(`--env 0` 保留)
|
|
456
475
|
- 除 `token-set` 外,其余命令都会从 `~/.xbbcli/config.env` 读取 `corpid`
|
|
457
476
|
- 大部分命令会从 `~/.xbbcli/config.env` 读取 `token`
|
|
458
|
-
-
|
|
477
|
+
- 环境变量 `XBB_*` 的优先级高于 `config.env`:`XBB_CORPID` / `XBB_TOKEN` 有值时,所有命令都以环境变量为准(`config.env` 中的启用公司被覆盖);两者都不存在时才没有任何生效配置。`XBB_ENV_ONLY=1` 可强制只用环境变量
|
|
459
478
|
- 所有命令会从配置中读取 `userId` 并附加到请求 header 中
|
|
460
479
|
- 大部分命令需要 formId 参数,可以根据业务名称或 businessType 从 `~/.xbbcli/<corpid>.formlist.json` 中获取 formId
|
|
461
480
|
- 需要查询部门 id/名称,或员工 userId/所属部门时,可以先查 `~/.xbbcli/<corpid>.department-user.json`(由 `token-set` 分页抓取并缓存全部部门与员工)
|
package/auth-login.js
CHANGED
package/auth-logout.js
CHANGED
|
@@ -61,7 +61,7 @@ function previewCorpids(companies) {
|
|
|
61
61
|
}
|
|
62
62
|
|
|
63
63
|
async function authLogout(kwargs) {
|
|
64
|
-
const alsoEnv =
|
|
64
|
+
const alsoEnv = String(kwargs.env ?? '1').trim() !== '0';
|
|
65
65
|
const companies = readCompanies();
|
|
66
66
|
const index = companies.findIndex((item) => item && item.enable === true);
|
|
67
67
|
const envPresent = hasEnvConfig();
|
|
@@ -76,7 +76,7 @@ async function authLogout(kwargs) {
|
|
|
76
76
|
? `本地共 ${companies.length} 家公司(${previewCorpids(companies)})且均未启用;要登出某家请先用 xbbcli token-use --corpid <CORPID> 启用,或改用 xbbcli token-del --corpid <CORPID> 直接删除`
|
|
77
77
|
: '本地没有任何公司配置';
|
|
78
78
|
const envNote = envPresent
|
|
79
|
-
? '环境变量 XBB_*
|
|
79
|
+
? '环境变量 XBB_* 仍存在,本次未清除(用 --env 1 或不指定该参数即可一并清除)'
|
|
80
80
|
: '环境变量 XBB_* 也不存在,本次无可清除内容';
|
|
81
81
|
const hint = `${scope};${envNote}`;
|
|
82
82
|
return createRow({
|
|
@@ -138,8 +138,8 @@ async function authLogout(kwargs) {
|
|
|
138
138
|
parts.push(`清除环境变量失败:${envFile}`);
|
|
139
139
|
}
|
|
140
140
|
} else if (envBefore) {
|
|
141
|
-
const forced = isEnvOnly() ? '且 XBB_ENV_ONLY=1 已强制只用环境变量' : '
|
|
142
|
-
parts.push(`注意:环境变量 XBB_*(corpid=${envBefore})仍然有效,${forced}
|
|
141
|
+
const forced = isEnvOnly() ? '且 XBB_ENV_ONLY=1 已强制只用环境变量' : '且其优先级高于 config.env,业务命令仍会使用它';
|
|
142
|
+
parts.push(`注意:环境变量 XBB_*(corpid=${envBefore})仍然有效,${forced},本次不算彻底登出;如需一并清除请改用 --env 1(或不指定该参数,auth-logout 默认会清除环境变量)`);
|
|
143
143
|
}
|
|
144
144
|
|
|
145
145
|
const remainingCompanies = target ? remaining : readCompanies();
|
|
@@ -166,8 +166,7 @@ cli({
|
|
|
166
166
|
browser: false,
|
|
167
167
|
domain: 'proapi.xbongbong.com',
|
|
168
168
|
args: [
|
|
169
|
-
{ name: 'env', type: '
|
|
170
|
-
{ name: 'noEnv', type: 'bool', default: false, help: '不清除环境变量,仅删除 config.env 中的激活配置(等价于 --env=false)' },
|
|
169
|
+
{ name: 'env', type: 'str', default: '1', help: '是否同时清除 XBB_* 环境变量:1 清除(默认),0 仅删除 config.env 保留环境变量。Windows 删除用户级变量,其他平台清理 ~/.xbbcli/env.sh' },
|
|
171
170
|
{ name: 'debug', type: 'bool', default: false, help: '输出配置文件、备份路径与生效的环境变量名(不打印 token)' },
|
|
172
171
|
],
|
|
173
172
|
columns: ['status', 'message', 'configFile', 'backupFile', 'corpid', 'corpName', 'userId', 'companyCount', 'hasActive', 'envStored', 'envFile', 'code', 'msg'],
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { cli, Strategy } from './xbb-registry.js';
|
|
2
|
+
import { readActiveConfig, isEnvActive } from './xbb-config.js';
|
|
3
|
+
import {
|
|
4
|
+
refreshLocalCaches,
|
|
5
|
+
getFormlistFile,
|
|
6
|
+
getCommandMapFile,
|
|
7
|
+
getDepartmentUserFile,
|
|
8
|
+
} from './xbb-token-store.js';
|
|
9
|
+
|
|
10
|
+
function trim(value) {
|
|
11
|
+
return String(value ?? '').trim();
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// 统一输出:成功时给出三份缓存与回填结果,失败时回落到合成错误行(不抛异常)
|
|
15
|
+
function makeRow(fields) {
|
|
16
|
+
return [{
|
|
17
|
+
status: '',
|
|
18
|
+
message: '',
|
|
19
|
+
corpid: '',
|
|
20
|
+
userId: '',
|
|
21
|
+
corpName: '',
|
|
22
|
+
userName: '',
|
|
23
|
+
formlistFile: '',
|
|
24
|
+
formlistCount: '',
|
|
25
|
+
commandMapFile: '',
|
|
26
|
+
departmentUserFile: '',
|
|
27
|
+
departmentCount: '',
|
|
28
|
+
userCount: '',
|
|
29
|
+
profileUpdated: '',
|
|
30
|
+
code: '',
|
|
31
|
+
msg: '',
|
|
32
|
+
...fields,
|
|
33
|
+
}];
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function makeErrorRow(code, msg, extra = {}) {
|
|
37
|
+
return makeRow({ status: 'error', code, msg, message: msg, ...extra });
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async function authRefreshCache(kwargs) {
|
|
41
|
+
const active = readActiveConfig();
|
|
42
|
+
const corpid = trim(active.corpid);
|
|
43
|
+
const userId = trim(kwargs.userId) || trim(active.userId);
|
|
44
|
+
|
|
45
|
+
if (kwargs.debug) {
|
|
46
|
+
const source = isEnvActive() ? 'env' : 'config';
|
|
47
|
+
process.stderr.write(`[debug] ConfigSource: ${source}\n[debug] Corpid: ${corpid || '(none)'}\n[debug] UserId: ${userId || '(none)'}\n[debug] FormlistFile: ${corpid ? getFormlistFile(corpid) : ''}\n[debug] CommandMapFile: ${corpid ? getCommandMapFile(corpid) : ''}\n[debug] DepartmentUserFile: ${corpid ? getDepartmentUserFile(corpid) : ''}\n`);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (!corpid) {
|
|
51
|
+
return makeErrorRow('NO_ACTIVE_CONFIG', '没有生效的配置:config.env 中不存在 enable=true 的公司,环境变量 XBB_* 也为空;请先执行 xbbcli auth-login 或 xbbcli token-set');
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
let cache;
|
|
55
|
+
try {
|
|
56
|
+
cache = await refreshLocalCaches(corpid, userId);
|
|
57
|
+
} catch (error) {
|
|
58
|
+
const partial = error.cache || {};
|
|
59
|
+
const step = trim(error.step) || 'unknown';
|
|
60
|
+
const message = `刷新本地缓存失败(步骤 ${step}):${error.message || error}`;
|
|
61
|
+
return makeRow({
|
|
62
|
+
status: 'error',
|
|
63
|
+
code: 'CACHE_REFRESH_FAILED',
|
|
64
|
+
msg: message,
|
|
65
|
+
message,
|
|
66
|
+
corpid,
|
|
67
|
+
userId,
|
|
68
|
+
corpName: partial.corpName ?? '',
|
|
69
|
+
userName: partial.userName ?? '',
|
|
70
|
+
formlistFile: partial.formlistFile ?? '',
|
|
71
|
+
formlistCount: partial.formlistCount ?? '',
|
|
72
|
+
commandMapFile: partial.commandMapFile ?? '',
|
|
73
|
+
departmentUserFile: partial.departmentUserFile ?? '',
|
|
74
|
+
departmentCount: partial.departmentCount ?? '',
|
|
75
|
+
userCount: partial.userCount ?? '',
|
|
76
|
+
profileUpdated: partial.profileUpdated ?? '',
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const message = cache.profileUpdated
|
|
81
|
+
? '已刷新表单清单缓存、命令映射与部门/员工缓存,并回填 corpName/userName 到 config.env'
|
|
82
|
+
: isEnvActive()
|
|
83
|
+
? '已刷新表单清单缓存、命令映射与部门/员工缓存;当前为环境变量模式,corpName/userName 未回填 config.env'
|
|
84
|
+
: '已刷新表单清单缓存、命令映射与部门/员工缓存;该公司不在 config.env 中,corpName/userName 未回填';
|
|
85
|
+
|
|
86
|
+
return makeRow({
|
|
87
|
+
status: 'ok',
|
|
88
|
+
message,
|
|
89
|
+
corpid,
|
|
90
|
+
userId,
|
|
91
|
+
corpName: cache.corpName,
|
|
92
|
+
userName: cache.userName,
|
|
93
|
+
formlistFile: cache.formlistFile,
|
|
94
|
+
formlistCount: cache.formlistCount,
|
|
95
|
+
commandMapFile: cache.commandMapFile,
|
|
96
|
+
departmentUserFile: cache.departmentUserFile,
|
|
97
|
+
departmentCount: cache.departmentCount,
|
|
98
|
+
userCount: cache.userCount,
|
|
99
|
+
profileUpdated: cache.profileUpdated,
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
cli({
|
|
104
|
+
site: 'xbb',
|
|
105
|
+
name: 'auth-refresh-cache',
|
|
106
|
+
description: '刷新当前启用公司的本地缓存(表单清单、命令映射等)',
|
|
107
|
+
strategy: Strategy.PUBLIC,
|
|
108
|
+
access: 'write',
|
|
109
|
+
browser: false,
|
|
110
|
+
domain: 'proapi.xbongbong.com',
|
|
111
|
+
args: [
|
|
112
|
+
{ name: 'userId', type: 'str', default: '', help: '操作人 id,用于从员工清单匹配 userName;默认取当前启用配置的 userId' },
|
|
113
|
+
{ name: 'debug', type: 'bool', default: false, help: '在 stderr 输出配置来源、corpid 与三份缓存文件路径' },
|
|
114
|
+
],
|
|
115
|
+
columns: ['status', 'message', 'corpid', 'userId', 'corpName', 'userName', 'formlistFile', 'formlistCount', 'commandMapFile', 'departmentUserFile', 'departmentCount', 'userCount', 'profileUpdated', 'code', 'msg'],
|
|
116
|
+
func: authRefreshCache,
|
|
117
|
+
});
|
package/auth-status.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import os from 'node:os';
|
|
3
3
|
import path from 'node:path';
|
|
4
|
-
import { cli, Strategy } from './xbb-registry.js';
|
|
5
|
-
import { readActiveConfig,
|
|
4
|
+
import { cli, Strategy, getRegistry } from './xbb-registry.js';
|
|
5
|
+
import { readActiveConfig, isEnvActive, isEnvOnly, getEnvVarNames } from './xbb-config.js';
|
|
6
6
|
|
|
7
7
|
const CONFIG_DIR = path.join(os.homedir(), '.xbbcli');
|
|
8
8
|
const CONFIG_FILE = path.join(CONFIG_DIR, 'config.env');
|
|
@@ -50,9 +50,35 @@ function withNotes(rows, notes) {
|
|
|
50
50
|
return [...rows, ...notes.map((value) => ({ key: 'note', value }))];
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
+
const VERIFY_VALID = '有效';
|
|
54
|
+
const VERIFY_INVALID = '无效-请重新配置token';
|
|
55
|
+
|
|
56
|
+
// 用一次轻量调用(等价于 user-list --pageSize 1)验证当前 token 是否有效
|
|
57
|
+
// 判定:业务错误码(code 非空且非 NO_DATA)或请求异常 → 无效;成功(含 NO_DATA 空清单)→ 有效
|
|
58
|
+
async function verifyToken() {
|
|
59
|
+
const command = getRegistry().get('xbb/user-list');
|
|
60
|
+
if (!command || typeof command.func !== 'function') {
|
|
61
|
+
return { valid: false, detail: '找不到 user-list 命令,无法验证' };
|
|
62
|
+
}
|
|
63
|
+
try {
|
|
64
|
+
const rows = await command.func({ page: '1', pageSize: '1' });
|
|
65
|
+
const list = Array.isArray(rows) ? rows : [];
|
|
66
|
+
const errorRow = list.find((item) => item && String(item.code ?? '') !== '');
|
|
67
|
+
if (!errorRow) return { valid: true, detail: 'user-list 调用成功' };
|
|
68
|
+
// NO_DATA 表示接口成功但清单为空,token 仍然有效
|
|
69
|
+
if (String(errorRow.code) === 'NO_DATA') return { valid: true, detail: 'user-list 调用成功(无数据)' };
|
|
70
|
+
return { valid: false, detail: `user-list 返回 ${errorRow.code} ${errorRow.msg || ''}`.trim() };
|
|
71
|
+
} catch (error) {
|
|
72
|
+
return { valid: false, detail: `user-list 调用异常:${String(error.message || error)}` };
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function withVerify(rows, verify) {
|
|
77
|
+
return [...rows, { key: 'token验证', value: verify.valid ? VERIFY_VALID : VERIFY_INVALID }];
|
|
78
|
+
}
|
|
79
|
+
|
|
53
80
|
async function authStatus(kwargs) {
|
|
54
81
|
const showToken = Boolean(kwargs.showToken);
|
|
55
|
-
const envActive = hasEnvConfig();
|
|
56
82
|
const envUsed = isEnvActive();
|
|
57
83
|
const active = readActiveConfig();
|
|
58
84
|
const companies = readCompanies();
|
|
@@ -67,31 +93,49 @@ async function authStatus(kwargs) {
|
|
|
67
93
|
const token = trim(active.token);
|
|
68
94
|
const rows = toRows({ ...active, token: showToken ? token : maskToken(token) });
|
|
69
95
|
|
|
96
|
+
// 没有生效配置或缺少 token 时无需发起调用,直接判定为无效
|
|
97
|
+
let verify;
|
|
98
|
+
if (source === 'none' || !token) {
|
|
99
|
+
verify = { valid: false, detail: source === 'none' ? '没有生效配置,无法验证' : '生效配置缺少 token,无法验证' };
|
|
100
|
+
} else {
|
|
101
|
+
verify = await verifyToken();
|
|
102
|
+
}
|
|
103
|
+
if (kwargs.debug) {
|
|
104
|
+
process.stderr.write(`[debug] Verify: ${verify.valid ? 'valid' : 'invalid'}(${verify.detail})\n`);
|
|
105
|
+
}
|
|
106
|
+
const verifiedRows = withVerify(rows, verify);
|
|
107
|
+
|
|
70
108
|
if (source === 'none') {
|
|
71
109
|
// 没有生效配置时不把残留的环境变量值当真值回显(XBB_BASEURL 等可能单独存在)
|
|
72
|
-
return withNotes(
|
|
110
|
+
return withNotes(verifiedRows, [
|
|
73
111
|
'没有处于激活状态的配置:config.env 中不存在 enable=true 的公司,环境变量 XBB_* 也为空;请先执行 xbbcli auth-login 或 xbbcli token-set',
|
|
74
112
|
]);
|
|
75
113
|
}
|
|
76
114
|
|
|
77
115
|
if (source === 'env') {
|
|
78
|
-
const
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
116
|
+
const enabledCount = companies.filter((item) => item.enable === true).length;
|
|
117
|
+
const reason = isEnvOnly()
|
|
118
|
+
? 'XBB_ENV_ONLY=1 已强制使用环境变量'
|
|
119
|
+
: enabledCount > 0
|
|
120
|
+
? '环境变量优先于 config.env,其中 enable=true 的公司被覆盖'
|
|
121
|
+
: 'config.env 中没有启用公司,回落到环境变量';
|
|
122
|
+
const notes = [`以上取值来自环境变量 XBB_*(${reason});环境变量模式不支持多公司切换,配置文件路径与公司数量见 --debug`];
|
|
123
|
+
if (!isEnvOnly() && enabledCount > 0) {
|
|
124
|
+
notes.push('如需改用 config.env 中的启用公司,请先清除 XBB_* 环境变量(或执行 xbbcli auth-logout)');
|
|
125
|
+
}
|
|
126
|
+
return withNotes(verifiedRows, notes);
|
|
82
127
|
}
|
|
83
128
|
|
|
84
129
|
const notes = [];
|
|
85
|
-
if (envActive) notes.push('环境变量 XBB_* 同时存在,但 config.env 启用项优先;如需彻底登出请执行 xbbcli auth-logout');
|
|
86
130
|
const enabledCount = companies.filter((item) => item.enable === true).length;
|
|
87
131
|
if (enabledCount > 1) notes.push(`config.env 中存在 ${enabledCount} 个 enable=true,仅第一条生效,请用 xbbcli token-use 修正`);
|
|
88
|
-
return withNotes(
|
|
132
|
+
return withNotes(verifiedRows, notes);
|
|
89
133
|
}
|
|
90
134
|
|
|
91
135
|
cli({
|
|
92
136
|
site: 'xbb',
|
|
93
137
|
name: 'auth-status',
|
|
94
|
-
description: '
|
|
138
|
+
description: '回显当前激活的配置,并验证 token 是否有效',
|
|
95
139
|
strategy: Strategy.PUBLIC,
|
|
96
140
|
access: 'read',
|
|
97
141
|
browser: false,
|
package/package.json
CHANGED
package/token-del.js
CHANGED
|
@@ -2,7 +2,7 @@ import fs from 'node:fs';
|
|
|
2
2
|
import os from 'node:os';
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import { cli, Strategy } from './xbb-registry.js';
|
|
5
|
-
import {
|
|
5
|
+
import { clearEnvVars, getEnvVarNames, hasEnvConfig, isEnvOnly, readEnvConfig } from './xbb-config.js';
|
|
6
6
|
|
|
7
7
|
const CONFIG_DIR = path.join(os.homedir(), '.xbbcli');
|
|
8
8
|
const CONFIG_FILE = path.join(CONFIG_DIR, 'config.env');
|
|
@@ -50,7 +50,7 @@ function maskToken(token) {
|
|
|
50
50
|
if (value.length <= 12) return `${value.slice(0, 4)}****`;
|
|
51
51
|
return `${value.slice(0, 8)}****${value.slice(-4)}`;
|
|
52
52
|
}
|
|
53
|
-
function createResult(status, message, corpid, enable, companyCount) {
|
|
53
|
+
function createResult(status, message, corpid, enable, companyCount, envStored = 'skipped', envFile = '') {
|
|
54
54
|
return [{
|
|
55
55
|
status,
|
|
56
56
|
message,
|
|
@@ -58,6 +58,8 @@ function createResult(status, message, corpid, enable, companyCount) {
|
|
|
58
58
|
corpid,
|
|
59
59
|
enable,
|
|
60
60
|
companyCount,
|
|
61
|
+
envStored,
|
|
62
|
+
envFile,
|
|
61
63
|
}];
|
|
62
64
|
}
|
|
63
65
|
|
|
@@ -67,9 +69,9 @@ async function delCompany(kwargs) {
|
|
|
67
69
|
return createResult('error', '缺少 --corpid', '', '', '');
|
|
68
70
|
}
|
|
69
71
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
72
|
+
const alsoEnv = String(kwargs.env ?? '1').trim() !== '0';
|
|
73
|
+
const envPresent = hasEnvConfig();
|
|
74
|
+
const envBefore = envPresent ? String(readEnvConfig().corpid || '').trim() : '';
|
|
73
75
|
|
|
74
76
|
const companies = readCompanies();
|
|
75
77
|
if (!companies.length) {
|
|
@@ -84,23 +86,61 @@ async function delCompany(kwargs) {
|
|
|
84
86
|
const next = remaining.length ? enableSingle(remaining) : [];
|
|
85
87
|
writeCompanies(next);
|
|
86
88
|
const enabledCorpid = next.find((item) => item.enable === true)?.corpid || '';
|
|
87
|
-
const
|
|
89
|
+
const parts = [next.length
|
|
88
90
|
? `已删除 corpid=${corpid},剩余 ${next.length} 家公司,当前启用 corpid=${enabledCorpid}`
|
|
89
|
-
: `已删除 corpid=${corpid}
|
|
90
|
-
|
|
91
|
+
: `已删除 corpid=${corpid},本地已无公司配置`];
|
|
92
|
+
|
|
93
|
+
let envStored = 'skipped';
|
|
94
|
+
let envFile = '';
|
|
95
|
+
|
|
96
|
+
if (alsoEnv) {
|
|
97
|
+
try {
|
|
98
|
+
const result = clearEnvVars();
|
|
99
|
+
envStored = result.stored;
|
|
100
|
+
envFile = result.file;
|
|
101
|
+
if (envStored === 'none') {
|
|
102
|
+
parts.push('未检测到 XBB_* 环境变量,无需删除');
|
|
103
|
+
} else {
|
|
104
|
+
const suffix = process.platform === 'win32' ? '(已打开的终端需重开才会消失)' : '';
|
|
105
|
+
parts.push(`环境变量 ${getEnvVarNames().join('、')} 已删除${suffix}`);
|
|
106
|
+
}
|
|
107
|
+
// 环境变量已清空,说明删除后的实际生效来源
|
|
108
|
+
if (envStored !== 'none' || envBefore) {
|
|
109
|
+
if (isEnvOnly()) {
|
|
110
|
+
parts.push('注意:XBB_ENV_ONLY=1 仍然开启,环境变量已清空,业务命令将报 NO_ACTIVE_CONFIG;如需恢复请先写入凭证或去掉该变量');
|
|
111
|
+
} else if (next.length) {
|
|
112
|
+
parts.push(`此后业务命令使用 config.env 中启用的 corpid=${enabledCorpid}`);
|
|
113
|
+
} else {
|
|
114
|
+
parts.push('此后没有可用凭证,请重新执行 xbbcli token-set 或 xbbcli auth-login');
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
} catch (error) {
|
|
118
|
+
envStored = 'failed';
|
|
119
|
+
envFile = String(error.message || error);
|
|
120
|
+
return createResult('partial', `${parts.join(';')};删除环境变量失败:${envFile}`, corpid, next.length ? 'true' : '', next.length, envStored, envFile);
|
|
121
|
+
}
|
|
122
|
+
} else if (envPresent) {
|
|
123
|
+
const forced = isEnvOnly()
|
|
124
|
+
? '且 XBB_ENV_ONLY=1 已强制只用环境变量(config.env 被忽略)'
|
|
125
|
+
: '且其优先级高于 config.env';
|
|
126
|
+
parts.push(`注意:环境变量 XBB_*(corpid=${envBefore || '未知'})仍然有效,${forced},业务命令仍会使用它;如需一并删除请改用 --env 1(或不指定该参数)`);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return createResult('ok', parts.join(';'), corpid, next.length ? 'true' : '', next.length, envStored, envFile);
|
|
91
130
|
}
|
|
92
131
|
|
|
93
132
|
cli({
|
|
94
133
|
site: 'xbb',
|
|
95
134
|
name: 'token-del',
|
|
96
|
-
description: '
|
|
135
|
+
description: '删除指定的已保存公司配置,并保证剩余配置中仅且只有一个被启用;默认同时删除 XBB_* 环境变量',
|
|
97
136
|
strategy: Strategy.PUBLIC,
|
|
98
137
|
access: 'write',
|
|
99
138
|
browser: false,
|
|
100
139
|
domain: 'proapi.xbongbong.com',
|
|
101
140
|
args: [
|
|
102
141
|
{ name: 'corpid', type: 'str', help: '要删除的公司 id,必须是已保存的 corpid(必填)' },
|
|
142
|
+
{ name: 'env', type: 'str', default: '1', help: '是否同时删除 XBB_* 环境变量:1 删除(默认),0 仅删除 config.env 保留环境变量。Windows 删除用户级变量,其他平台清理 ~/.xbbcli/env.sh' },
|
|
103
143
|
],
|
|
104
|
-
columns: ['status', 'message', 'configFile', 'corpid', 'enable', 'companyCount'],
|
|
144
|
+
columns: ['status', 'message', 'configFile', 'corpid', 'enable', 'companyCount', 'envStored', 'envFile'],
|
|
105
145
|
func: delCompany,
|
|
106
146
|
});
|
package/token-list.js
CHANGED
|
@@ -67,40 +67,59 @@ cli({
|
|
|
67
67
|
],
|
|
68
68
|
columns: ['enable', 'corpid', 'userId', 'baseurl', 'token', 'corpName', 'userName', 'code', 'msg', 'source'],
|
|
69
69
|
func: async (kwargs) => {
|
|
70
|
+
const showToken = Boolean(kwargs.showToken);
|
|
70
71
|
const companies = readCompanies();
|
|
71
72
|
const envConfig = readEnvConfig();
|
|
72
|
-
|
|
73
|
-
|
|
73
|
+
const envPresent = hasEnvConfig(envConfig);
|
|
74
|
+
const envOnly = isEnvOnly();
|
|
75
|
+
const rows = [];
|
|
76
|
+
|
|
77
|
+
// 环境变量行:XBB_CORPID / XBB_TOKEN 有值就回显,source=env(优先级高于 config.env)
|
|
78
|
+
if (envPresent) {
|
|
79
|
+
rows.push({
|
|
74
80
|
enable: 'true',
|
|
75
81
|
corpid: String(envConfig.corpid || ''),
|
|
76
82
|
userId: String(envConfig.userId || ''),
|
|
77
83
|
baseurl: String(envConfig.baseurl || ''),
|
|
78
|
-
token:
|
|
84
|
+
token: showToken ? String(envConfig.token || '') : maskToken(envConfig.token),
|
|
79
85
|
corpName: String(envConfig.corpName || ''),
|
|
80
86
|
userName: String(envConfig.userName || ''),
|
|
81
87
|
code: '',
|
|
82
|
-
msg:
|
|
88
|
+
msg: envOnly
|
|
89
|
+
? '来源为环境变量 XBB_*(XBB_ENV_ONLY=1 已强制只用环境变量,config.env 被忽略)'
|
|
90
|
+
: '来源为环境变量 XBB_*(优先级高于 config.env,当前生效)',
|
|
83
91
|
source: 'env',
|
|
84
|
-
}
|
|
92
|
+
});
|
|
85
93
|
}
|
|
86
|
-
|
|
94
|
+
|
|
95
|
+
const enabledCount = companies.filter((item) => item.enable === true).length;
|
|
96
|
+
for (const item of companies) {
|
|
97
|
+
const isEnabled = item.enable === true;
|
|
98
|
+
rows.push({
|
|
99
|
+
enable: isEnabled ? 'true' : 'false',
|
|
100
|
+
corpid: String(item.corpid || ''),
|
|
101
|
+
userId: String(item.userId || ''),
|
|
102
|
+
baseurl: String(item.baseurl || ''),
|
|
103
|
+
token: showToken ? String(item.token || '') : maskToken(item.token),
|
|
104
|
+
corpName: String(item.corpName || ''),
|
|
105
|
+
userName: String(item.userName || ''),
|
|
106
|
+
code: '',
|
|
107
|
+
msg: isEnabled
|
|
108
|
+
? envOnly
|
|
109
|
+
? '来源为 config.env,但 XBB_ENV_ONLY=1 已将其忽略,当前不生效'
|
|
110
|
+
: envPresent
|
|
111
|
+
? '来源为 config.env,但已被环境变量 XBB_* 覆盖,当前不生效'
|
|
112
|
+
: '来源为 config.env,当前生效'
|
|
113
|
+
: '',
|
|
114
|
+
source: 'config',
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (!rows.length) {
|
|
87
119
|
return makeErrorRow('NO_CONFIG', '缺少本地配置;请先执行 xbbcli token-set --corpid <CORPID> --token <TOKEN> --userId <USERID>');
|
|
88
120
|
}
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
const rows = companies.map((item) => ({
|
|
92
|
-
enable: item.enable === true ? 'true' : 'false',
|
|
93
|
-
corpid: String(item.corpid || ''),
|
|
94
|
-
userId: String(item.userId || ''),
|
|
95
|
-
baseurl: String(item.baseurl || ''),
|
|
96
|
-
token: showToken ? String(item.token || '') : maskToken(item.token),
|
|
97
|
-
corpName: String(item.corpName || ''),
|
|
98
|
-
userName: String(item.userName || ''),
|
|
99
|
-
code: '',
|
|
100
|
-
msg: '',
|
|
101
|
-
source: 'config',
|
|
102
|
-
}));
|
|
103
|
-
if (enabledCount !== 1) {
|
|
121
|
+
|
|
122
|
+
if (companies.length && enabledCount !== 1) {
|
|
104
123
|
rows.push({
|
|
105
124
|
enable: '',
|
|
106
125
|
corpid: '',
|
package/token-use.js
CHANGED
|
@@ -2,7 +2,8 @@ import fs from 'node:fs';
|
|
|
2
2
|
import os from 'node:os';
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import { cli, Strategy } from './xbb-registry.js';
|
|
5
|
-
import {
|
|
5
|
+
import { hasEnvConfig, isEnvOnly, getEnvVarNames, persistEnvVars } from './xbb-config.js';
|
|
6
|
+
import { resolveBaseUrl } from './xbb-token-store.js';
|
|
6
7
|
|
|
7
8
|
const CONFIG_DIR = path.join(os.homedir(), '.xbbcli');
|
|
8
9
|
const CONFIG_FILE = path.join(CONFIG_DIR, 'config.env');
|
|
@@ -67,8 +68,10 @@ async function useCompany(kwargs) {
|
|
|
67
68
|
return createResult('error', '缺少 --corpid', '', '', '');
|
|
68
69
|
}
|
|
69
70
|
|
|
70
|
-
|
|
71
|
-
|
|
71
|
+
const envPresent = hasEnvConfig();
|
|
72
|
+
// XBB_ENV_ONLY=1 且 XBB_* 全空:config.env 被强制忽略,写入也无法生效
|
|
73
|
+
if (isEnvOnly() && !envPresent) {
|
|
74
|
+
return createResult('error', `当前 XBB_ENV_ONLY=1 已强制只用环境变量,但 ${getEnvVarNames().join(', ')} 均为空;请先执行 xbbcli token-set --env 1 写入环境变量`, corpid, '', '');
|
|
72
75
|
}
|
|
73
76
|
|
|
74
77
|
const companies = readCompanies();
|
|
@@ -81,15 +84,45 @@ async function useCompany(kwargs) {
|
|
|
81
84
|
return createResult('error', `未找到 corpid=${corpid} 的本地配置`, corpid, '', companies.length);
|
|
82
85
|
}
|
|
83
86
|
|
|
87
|
+
const target = companies[index];
|
|
88
|
+
// 同步 XBB_* 需要完整凭证:缺 token 时直接拒绝,避免残留上一家公司的 XBB_TOKEN 被继续使用
|
|
89
|
+
if (envPresent && !String(target.token || '').trim()) {
|
|
90
|
+
return createResult('error', `corpid=${corpid} 的本地配置缺少 token,无法同步到 XBB_* 环境变量;请先执行 xbbcli token-set --corpid ${corpid} --token <TOKEN> --userId <USERID>`, corpid, '', companies.length);
|
|
91
|
+
}
|
|
92
|
+
|
|
84
93
|
const next = enableOnly(companies, index);
|
|
85
94
|
writeCompanies(next);
|
|
86
|
-
|
|
95
|
+
|
|
96
|
+
const parts = [`已启用 corpid=${corpid},其余公司已标记为 enable=false`];
|
|
97
|
+
|
|
98
|
+
// 环境变量中有值时把生效公司同步写回 XBB_*,否则切换会被优先级更高的环境变量覆盖
|
|
99
|
+
if (envPresent) {
|
|
100
|
+
try {
|
|
101
|
+
persistEnvVars({
|
|
102
|
+
corpid: target.corpid,
|
|
103
|
+
token: target.token,
|
|
104
|
+
// 条目缺少 baseurl 时按 corpid 规则推导,避免残留上一家公司的 XBB_BASEURL
|
|
105
|
+
baseurl: target.baseurl || resolveBaseUrl(target.corpid),
|
|
106
|
+
userId: target.userId,
|
|
107
|
+
corpName: target.corpName,
|
|
108
|
+
userName: target.userName,
|
|
109
|
+
});
|
|
110
|
+
const suffix = process.platform === 'win32'
|
|
111
|
+
? '(Windows 需重开终端后才生效)'
|
|
112
|
+
: '(重新 source ~/.xbbcli/env.sh 后才生效)';
|
|
113
|
+
parts.push(`检测到环境变量 XBB_* 有值,已同步更新为该公司凭证${suffix}`);
|
|
114
|
+
} catch (error) {
|
|
115
|
+
return createResult('partial', `已启用 corpid=${corpid},但同步更新环境变量失败:${String(error.message || error)}`, corpid, 'true', next.length);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return createResult('ok', parts.join(';'), corpid, 'true', next.length);
|
|
87
120
|
}
|
|
88
121
|
|
|
89
122
|
cli({
|
|
90
123
|
site: 'xbb',
|
|
91
124
|
name: 'token-use',
|
|
92
|
-
description: '切换当前启用的公司(enable
|
|
125
|
+
description: '切换当前启用的公司(enable),保证仅且只有一个公司被启用;XBB_* 环境变量有值时同步更新为该公司凭证',
|
|
93
126
|
strategy: Strategy.PUBLIC,
|
|
94
127
|
access: 'write',
|
|
95
128
|
browser: false,
|
package/xbb-config.js
CHANGED
|
@@ -62,25 +62,21 @@ function readFileConfig() {
|
|
|
62
62
|
}
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
-
//
|
|
65
|
+
// 环境变量优先:XBB_* 中 corpid/token 非空时只用环境变量,否则用 config.env 里 enable=true 的公司
|
|
66
66
|
export function readActiveConfig() {
|
|
67
67
|
const envConfig = readEnvConfig();
|
|
68
68
|
if (isEnvOnly()) {
|
|
69
69
|
return envConfig;
|
|
70
70
|
}
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
return fileConfig;
|
|
71
|
+
if (hasEnvConfig(envConfig)) {
|
|
72
|
+
return envConfig;
|
|
74
73
|
}
|
|
75
|
-
return
|
|
74
|
+
return readFileConfig();
|
|
76
75
|
}
|
|
77
76
|
|
|
78
77
|
// 当前生效来源是否为环境变量
|
|
79
78
|
export function isEnvActive() {
|
|
80
|
-
|
|
81
|
-
return hasEnvConfig();
|
|
82
|
-
}
|
|
83
|
-
return Object.keys(readFileConfig()).length === 0 && hasEnvConfig();
|
|
79
|
+
return hasEnvConfig();
|
|
84
80
|
}
|
|
85
81
|
|
|
86
82
|
export function getEnvVarNames() {
|
package/xbb-token-store.js
CHANGED
|
@@ -2,7 +2,7 @@ import fs from 'node:fs';
|
|
|
2
2
|
import os from 'node:os';
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import { getRegistry } from './xbb-registry.js';
|
|
5
|
-
import { persistEnvVars } from './xbb-config.js';
|
|
5
|
+
import { persistEnvVars, hasEnvConfig } from './xbb-config.js';
|
|
6
6
|
|
|
7
7
|
// token-set 与 auth-login 共用的凭证落盘逻辑:写 config.env、表单缓存、命令映射、部门/员工缓存、环境变量
|
|
8
8
|
export const CONFIG_DIR = path.join(os.homedir(), '.xbbcli');
|
|
@@ -109,7 +109,7 @@ async function writeFormlistFile(corpid) {
|
|
|
109
109
|
const mergedRows = [...customForms, ...systemForms];
|
|
110
110
|
|
|
111
111
|
fs.writeFileSync(formlistFile, JSON.stringify(mergedRows, null, 2) + '\n', 'utf8');
|
|
112
|
-
return formlistFile;
|
|
112
|
+
return { file: formlistFile, count: mergedRows.length };
|
|
113
113
|
}
|
|
114
114
|
|
|
115
115
|
async function fetchAllRows(name, pageSize) {
|
|
@@ -178,7 +178,7 @@ async function writeDepartmentUserFile(corpid, userId) {
|
|
|
178
178
|
const file = getDepartmentUserFile(corpid);
|
|
179
179
|
fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
180
180
|
fs.writeFileSync(file, JSON.stringify(payload, null, 2) + '\n', 'utf8');
|
|
181
|
-
return { file, corpName: getCorpName(departmentRows), userName: getUserName(userRows, userId) };
|
|
181
|
+
return { file, corpName: getCorpName(departmentRows), userName: getUserName(userRows, userId), departmentCount: departmentRows.length, userCount: userRows.length };
|
|
182
182
|
}
|
|
183
183
|
|
|
184
184
|
function getCorpName(departmentRows) {
|
|
@@ -196,16 +196,89 @@ function getUserName(userRows, userId) {
|
|
|
196
196
|
return matched ? String(matched.name || '').trim() : '';
|
|
197
197
|
}
|
|
198
198
|
|
|
199
|
+
// 把 corpName / userName 合并进 config.env 对应公司,返回是否真的写过文件
|
|
200
|
+
// config.env 里没有这家公司的条目时(纯环境变量模式)直接跳过,避免凭空生成一份只有 corpName 的 config.env
|
|
199
201
|
export function writeCompanyProfile(corpid, profile) {
|
|
202
|
+
const target = String(corpid || '').trim();
|
|
200
203
|
const fields = Object.entries(profile).filter(([, value]) => String(value || '').trim() !== '');
|
|
201
|
-
if (!fields.length) {
|
|
202
|
-
return;
|
|
204
|
+
if (!target || !fields.length) {
|
|
205
|
+
return false;
|
|
206
|
+
}
|
|
207
|
+
const companies = readCompanies();
|
|
208
|
+
const index = companies.findIndex((item) => String(item.corpid || '').trim() === target);
|
|
209
|
+
if (index === -1) {
|
|
210
|
+
return false;
|
|
203
211
|
}
|
|
204
212
|
const patch = Object.fromEntries(fields);
|
|
205
|
-
const
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
213
|
+
const next = companies.map((item, itemIndex) => (itemIndex === index ? { ...item, ...patch } : item));
|
|
214
|
+
fs.writeFileSync(CONFIG_FILE, JSON.stringify(next, null, 2) + '\n', 'utf8');
|
|
215
|
+
return true;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// token-set / auth-login / auth-refresh-cache 共用:刷新三份本地缓存并回填 corpName、userName
|
|
219
|
+
// 失败时抛出的 error 上带 step(formlist/command-map/department-user/profile)与已完成部分的 cache,便于命令回显进度
|
|
220
|
+
export async function refreshLocalCaches(corpid, userId) {
|
|
221
|
+
const target = String(corpid || '').trim();
|
|
222
|
+
if (!target) {
|
|
223
|
+
const error = new Error('缺少 corpid,无法确定要刷新哪家公司的缓存');
|
|
224
|
+
error.step = 'formlist';
|
|
225
|
+
error.cache = { formlistFile: '', commandMapFile: '', departmentUserFile: '', formlistCount: '', departmentCount: '', userCount: '', corpName: '', userName: '', profileUpdated: false };
|
|
226
|
+
throw error;
|
|
227
|
+
}
|
|
228
|
+
const cache = {
|
|
229
|
+
formlistFile: getFormlistFile(target),
|
|
230
|
+
commandMapFile: '',
|
|
231
|
+
departmentUserFile: '',
|
|
232
|
+
formlistCount: '',
|
|
233
|
+
departmentCount: '',
|
|
234
|
+
userCount: '',
|
|
235
|
+
corpName: '',
|
|
236
|
+
userName: '',
|
|
237
|
+
profileUpdated: false,
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
241
|
+
|
|
242
|
+
try {
|
|
243
|
+
const formlist = await writeFormlistFile(target);
|
|
244
|
+
cache.formlistFile = formlist.file;
|
|
245
|
+
cache.formlistCount = formlist.count;
|
|
246
|
+
} catch (error) {
|
|
247
|
+
error.step = 'formlist';
|
|
248
|
+
error.cache = cache;
|
|
249
|
+
throw error;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
try {
|
|
253
|
+
cache.commandMapFile = writeCommandMap(target);
|
|
254
|
+
} catch (error) {
|
|
255
|
+
error.step = 'command-map';
|
|
256
|
+
error.cache = cache;
|
|
257
|
+
throw error;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
try {
|
|
261
|
+
const departmentUser = await writeDepartmentUserFile(target, userId);
|
|
262
|
+
cache.departmentUserFile = departmentUser.file;
|
|
263
|
+
cache.corpName = departmentUser.corpName;
|
|
264
|
+
cache.userName = departmentUser.userName;
|
|
265
|
+
cache.departmentCount = departmentUser.departmentCount;
|
|
266
|
+
cache.userCount = departmentUser.userCount;
|
|
267
|
+
} catch (error) {
|
|
268
|
+
error.step = 'department-user';
|
|
269
|
+
error.cache = cache;
|
|
270
|
+
throw error;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
try {
|
|
274
|
+
cache.profileUpdated = writeCompanyProfile(target, { corpName: cache.corpName, userName: cache.userName });
|
|
275
|
+
} catch (error) {
|
|
276
|
+
error.step = 'profile';
|
|
277
|
+
error.cache = cache;
|
|
278
|
+
throw error;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
return cache;
|
|
209
282
|
}
|
|
210
283
|
|
|
211
284
|
function normalizeBusinessType(value) {
|
|
@@ -372,16 +445,19 @@ export async function saveCompanyCredentials(kwargs) {
|
|
|
372
445
|
fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
373
446
|
fs.writeFileSync(CONFIG_FILE, JSON.stringify(companies, null, 2) + '\n', 'utf8');
|
|
374
447
|
|
|
448
|
+
// 环境变量优先级高于 config.env:此时只写文件不会改变业务命令实际使用的凭证,需在结果里提示
|
|
449
|
+
const envShadow = hasEnvConfig()
|
|
450
|
+
? ';注意:检测到 XBB_* 环境变量,其优先级高于 config.env,业务命令仍会使用环境变量;如需改用本次保存的配置,请清除 XBB_* 环境变量或执行 xbbcli auth-logout'
|
|
451
|
+
: '';
|
|
452
|
+
|
|
375
453
|
try {
|
|
376
|
-
const
|
|
377
|
-
const commandMapFile =
|
|
378
|
-
|
|
379
|
-
writeCompanyProfile(corpid, { corpName, userName });
|
|
380
|
-
return createResult('ok', '已保存 corpid、token、baseurl、userId,并同步表单模板缓存、命令映射文件与部门/员工缓存', corpid, baseurl, userId, formlistFile, commandMapFile, true, companies.length, departmentUserFile, corpName, userName, 'skipped', '');
|
|
454
|
+
const cache = await refreshLocalCaches(corpid, userId);
|
|
455
|
+
const { formlistFile, commandMapFile, departmentUserFile, corpName, userName } = cache;
|
|
456
|
+
return createResult('ok', `已保存 corpid、token、baseurl、userId,并同步表单模板缓存、命令映射文件与部门/员工缓存${envShadow}`, corpid, baseurl, userId, formlistFile, commandMapFile, true, companies.length, departmentUserFile, corpName, userName, 'skipped', '');
|
|
381
457
|
} catch (error) {
|
|
382
458
|
return createResult(
|
|
383
459
|
'partial',
|
|
384
|
-
`已保存 corpid、token、baseurl、userId
|
|
460
|
+
`已保存 corpid、token、baseurl、userId,但同步缓存失败${error.step ? `(步骤 ${error.step})` : ''}:${error.message}${envShadow}`,
|
|
385
461
|
corpid,
|
|
386
462
|
baseurl,
|
|
387
463
|
userId,
|