@xbongbong/xbbcli 2.1.1 → 2.4.0
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 +97 -6
- package/auth-login.js +306 -0
- package/auth-logout.js +175 -0
- package/auth-status.js +105 -0
- package/bin/xbbcli.js +15 -2
- package/buyer-inquiry-detail.js +2 -14
- package/buyer-inquiry-list.js +2 -14
- package/clue-add-couser.js +2 -14
- package/clue-add.js +2 -14
- package/clue-back.js +2 -14
- package/clue-del.js +2 -14
- package/clue-delete-couser.js +2 -14
- package/clue-delete-mainuser.js +2 -14
- package/clue-detail.js +2 -14
- package/clue-distribution.js +2 -14
- package/clue-edit.js +2 -14
- package/clue-handover.js +3 -15
- package/clue-list.js +2 -14
- package/clue-thorough-delete.js +2 -14
- package/communicate-add.js +2 -14
- package/communicate-del.js +2 -14
- package/communicate-detail.js +2 -14
- package/communicate-edit.js +2 -14
- package/communicate-list.js +2 -14
- package/communicate-plan-add.js +2 -14
- package/communicate-plan-cancel.js +2 -14
- package/communicate-plan-del.js +2 -14
- package/communicate-plan-detail.js +2 -14
- package/communicate-plan-list.js +2 -14
- package/communicate-plan-single-operate.js +2 -14
- package/contact-add-couser.js +2 -14
- package/contact-add-mainuser.js +2 -14
- package/contact-add.js +2 -14
- package/contact-del.js +2 -14
- package/contact-delete-couser.js +2 -14
- package/contact-delete-mainuser.js +2 -14
- package/contact-detail.js +2 -14
- package/contact-edit.js +2 -14
- package/contact-handover.js +2 -14
- package/contact-list.js +2 -14
- package/contract-add-couser.js +2 -14
- package/contract-add-mainuser.js +2 -14
- package/contract-add.js +2 -14
- package/contract-del.js +2 -14
- package/contract-delete-couser.js +2 -14
- package/contract-delete-mainuser.js +2 -14
- package/contract-detail.js +2 -14
- package/contract-edit.js +2 -14
- package/contract-handover.js +2 -14
- package/contract-list.js +2 -14
- package/contract-outstock-add.js +2 -14
- package/contract-outstock-del.js +2 -14
- package/contract-outstock-detail.js +2 -14
- package/contract-outstock-edit.js +2 -14
- package/contract-outstock-list.js +2 -14
- package/customer-add-couser.js +2 -14
- package/customer-add.js +2 -14
- package/customer-back.js +2 -14
- package/customer-del.js +2 -14
- package/customer-delete-couser.js +2 -14
- package/customer-delete-mainuser.js +2 -14
- package/customer-detail.js +2 -14
- package/customer-distribution.js +2 -14
- package/customer-edit.js +2 -14
- package/customer-handover.js +2 -14
- package/customer-invoice-address-add.js +2 -14
- package/customer-invoice-address-edit.js +2 -14
- package/customer-invoice-address.js +2 -14
- package/customer-invoice-info-add.js +2 -14
- package/customer-invoice-info-edit.js +2 -14
- package/customer-invoice-info.js +2 -14
- package/customer-list.js +2 -14
- package/department-add.js +2 -14
- package/department-del.js +2 -14
- package/department-edit.js +2 -14
- package/department-list.js +2 -14
- package/form-data-add-couser.js +2 -14
- package/form-data-add-mainuser.js +2 -14
- package/form-data-add.js +2 -14
- package/form-data-del.js +2 -14
- package/form-data-delete-couser.js +2 -14
- package/form-data-delete-mainuser.js +2 -14
- package/form-data-detail.js +2 -14
- package/form-data-edit.js +2 -14
- package/form-data-handover.js +2 -14
- package/form-data-list.js +2 -14
- package/form-get.js +2 -14
- package/form-list.js +2 -14
- package/{ai-call-add-task.js → hkh-call-add-task.js} +4 -16
- package/{ai-word-list.js → hkh-word-list.js} +4 -16
- package/invoice-add.js +3 -15
- package/invoice-cancel.js +3 -15
- package/invoice-del.js +3 -15
- package/invoice-detail.js +3 -15
- package/invoice-edit.js +3 -15
- package/invoice-get-amount-detail.js +3 -15
- package/invoice-list.js +3 -15
- package/invoice-red.js +3 -15
- package/label-add.js +3 -15
- package/label-all-list.js +3 -15
- package/label-batch-add.js +3 -15
- package/label-batch-remove.js +3 -15
- package/label-del-completely.js +3 -15
- package/label-del.js +3 -15
- package/label-edit.js +3 -15
- package/label-group-add.js +3 -15
- package/label-group-del.js +3 -15
- package/label-group-list.js +3 -15
- package/label-group-update.js +3 -15
- package/label-list.js +3 -15
- package/label-reduction.js +3 -15
- package/label-repeat.js +3 -15
- package/market-activity-add.js +2 -14
- package/market-activity-del.js +2 -14
- package/market-activity-detail.js +2 -14
- package/market-activity-edit.js +2 -14
- package/market-activity-handover.js +2 -14
- package/market-activity-list.js +2 -14
- package/opportunity-add-couser.js +2 -14
- package/opportunity-add-mainuser.js +2 -14
- package/opportunity-add.js +2 -14
- package/opportunity-del.js +2 -14
- package/opportunity-delete-couser.js +2 -14
- package/opportunity-delete-mainuser.js +2 -14
- package/opportunity-detail.js +2 -14
- package/opportunity-edit.js +2 -14
- package/opportunity-handover.js +2 -14
- package/opportunity-list.js +2 -14
- package/package.json +2 -2
- package/pay-plan-add-couser.js +2 -14
- package/pay-plan-add-mainuser.js +2 -14
- package/pay-plan-add.js +2 -14
- package/pay-plan-delete-couser.js +2 -14
- package/pay-plan-delete-mainuser.js +2 -14
- package/pay-plan-handover.js +2 -14
- package/pay-sheet-add-bad.js +2 -14
- package/pay-sheet-add-couser.js +2 -14
- package/pay-sheet-add-mainuser.js +2 -14
- package/pay-sheet-add-red.js +2 -14
- package/pay-sheet-add-write-off.js +2 -14
- package/pay-sheet-del.js +2 -14
- package/pay-sheet-delete-couser.js +2 -14
- package/pay-sheet-delete-mainuser.js +2 -14
- package/pay-sheet-detail.js +2 -14
- package/pay-sheet-edit-bad.js +2 -14
- package/pay-sheet-edit-red.js +2 -14
- package/pay-sheet-edit-write-off.js +2 -14
- package/pay-sheet-get-amount-detail.js +2 -14
- package/pay-sheet-list.js +2 -14
- package/payment-add-couser.js +2 -14
- package/payment-add-mainuser.js +2 -14
- package/payment-add.js +2 -14
- package/payment-del.js +2 -14
- package/payment-delete-couser.js +2 -14
- package/payment-delete-mainuser.js +2 -14
- package/payment-detail.js +2 -14
- package/payment-edit.js +2 -14
- package/payment-handover.js +2 -14
- package/payment-list.js +2 -14
- package/payment-sheet-add-bad-debt.js +2 -14
- package/payment-sheet-add-pre.js +2 -14
- package/payment-sheet-add-red.js +2 -14
- package/payment-sheet-add-write-off.js +2 -14
- package/payment-sheet-del.js +2 -14
- package/payment-sheet-detail.js +2 -14
- package/payment-sheet-edit-bad-debt.js +2 -14
- package/payment-sheet-edit-red.js +2 -14
- package/payment-sheet-edit-write-off.js +2 -14
- package/payment-sheet-get-amount-detail.js +2 -14
- package/payment-sheet-handover.js +2 -14
- package/payment-sheet-list.js +2 -14
- package/product-add.js +2 -14
- package/product-category-add.js +2 -14
- package/product-category-del.js +2 -14
- package/product-category-list.js +2 -14
- package/product-category-update.js +2 -14
- package/product-del.js +2 -14
- package/product-detail.js +2 -14
- package/product-edit.js +2 -14
- package/product-list.js +2 -14
- package/product-online.js +2 -14
- package/quotation-add-couser.js +3 -15
- package/quotation-add-mainuser.js +3 -15
- package/quotation-add.js +3 -15
- package/quotation-del.js +3 -15
- package/quotation-delete-couser.js +3 -15
- package/quotation-delete-mainuser.js +3 -15
- package/quotation-detail.js +3 -15
- package/quotation-edit.js +3 -15
- package/quotation-handover.js +2 -14
- package/quotation-list.js +3 -15
- package/refund-add.js +2 -14
- package/refund-del.js +2 -14
- package/refund-detail.js +2 -14
- package/refund-edit.js +2 -14
- package/refund-list.js +2 -14
- package/role-list.js +2 -14
- package/sign-in-list.js +2 -14
- package/stage-get.js +2 -14
- package/stage-jump.js +2 -14
- package/supplier-add.js +2 -14
- package/token-del.js +5 -0
- package/token-generate.js +2 -14
- package/token-list.js +20 -2
- package/token-set.js +6 -374
- package/token-use.js +5 -0
- package/user-add.js +2 -14
- package/user-del.js +2 -14
- package/user-edit.js +2 -14
- package/user-handover.js +2 -14
- package/user-list.js +2 -14
- package/work-order-add.js +2 -14
- package/work-order-asset-add-couser.js +3 -15
- package/work-order-asset-add-mainuser.js +3 -15
- package/work-order-asset-add.js +3 -15
- package/work-order-asset-del-couser.js +3 -15
- package/work-order-asset-del-mainuser.js +3 -15
- package/work-order-asset-del.js +3 -15
- package/work-order-asset-detail.js +3 -15
- package/work-order-asset-edit.js +3 -15
- package/work-order-asset-handover.js +3 -15
- package/work-order-asset-list.js +3 -15
- package/work-order-del.js +2 -14
- package/work-order-detail.js +2 -14
- package/work-order-dynamic-list.js +2 -14
- package/work-order-edit.js +2 -14
- package/work-order-list.js +2 -14
- package/work-order-operate.js +2 -14
- package/work-order-template-detail.js +2 -14
- package/work-order-template-list.js +2 -14
- package/work-report-daily-add.js +2 -14
- package/work-report-daily-del.js +2 -14
- package/work-report-daily-detail.js +2 -14
- package/work-report-daily-edit.js +2 -14
- package/work-report-daily-get-work-plan.js +2 -14
- package/work-report-daily-list.js +2 -14
- package/work-report-monthly-add.js +2 -14
- package/work-report-monthly-del.js +2 -14
- package/work-report-monthly-detail.js +2 -14
- package/work-report-monthly-edit.js +2 -14
- package/work-report-monthly-list.js +2 -14
- package/work-report-weekly-add.js +2 -14
- package/work-report-weekly-del.js +2 -14
- package/work-report-weekly-detail.js +2 -14
- package/work-report-weekly-edit.js +2 -14
- package/work-report-weekly-list.js +2 -14
- package/work-task-add-couser.js +2 -14
- package/work-task-add.js +2 -14
- package/work-task-delete-couser.js +2 -14
- package/work-task-detail.js +2 -14
- package/work-task-edit.js +2 -14
- package/work-task-handover.js +2 -14
- package/workorder-add-couser.js +2 -14
- package/workorder-delete-couser.js +2 -14
- package/workorder-handover.js +2 -14
- package/workorderlist.js +2 -14
- package/workorderproductlist.js +2 -14
- package/worktimerecorddetail.js +2 -14
- package/worktimerecordlist.js +2 -14
- package/xbb-browser.js +330 -0
- package/xbb-config.js +146 -0
- package/xbb-token-store.js +389 -0
- package/version.js +0 -18
package/xbb-config.js
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import os from 'node:os';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { spawnSync } from 'node:child_process';
|
|
5
|
+
|
|
6
|
+
const CONFIG_DIR = path.join(os.homedir(), '.xbbcli');
|
|
7
|
+
const CONFIG_FILE = path.join(CONFIG_DIR, 'config.env');
|
|
8
|
+
const ENV_FILE = path.join(CONFIG_DIR, 'env.sh');
|
|
9
|
+
|
|
10
|
+
// config.env 字段名 -> 对应环境变量名(环境变量模式只支持单公司)
|
|
11
|
+
export const ENV_VAR_NAMES = Object.freeze({
|
|
12
|
+
corpid: 'XBB_CORPID',
|
|
13
|
+
token: 'XBB_TOKEN',
|
|
14
|
+
baseurl: 'XBB_BASEURL',
|
|
15
|
+
userId: 'XBB_USERID',
|
|
16
|
+
corpName: 'XBB_CORPNAME',
|
|
17
|
+
userName: 'XBB_USERNAME',
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
function trim(value) {
|
|
21
|
+
return String(value ?? '').trim();
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function isTruthy(value) {
|
|
25
|
+
return ['1', 'true', 'yes', 'on'].includes(trim(value).toLowerCase());
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// 读取环境中非空的 XBB_* 变量,返回与 config.env 单条配置同构的对象
|
|
29
|
+
export function readEnvConfig() {
|
|
30
|
+
const config = {};
|
|
31
|
+
for (const [field, name] of Object.entries(ENV_VAR_NAMES)) {
|
|
32
|
+
const value = trim(process.env[name]);
|
|
33
|
+
if (value) config[field] = value;
|
|
34
|
+
}
|
|
35
|
+
return config;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function hasEnvConfig(config = readEnvConfig()) {
|
|
39
|
+
return Boolean(trim(config.corpid) || trim(config.token));
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// XBB_ENV_ONLY=1 时强制只使用环境变量,忽略 config.env
|
|
43
|
+
export function isEnvOnly() {
|
|
44
|
+
return isTruthy(process.env.XBB_ENV_ONLY);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// 与旧版每个命令内联的 readConfig 行为保持一致:优先返回 enable=true 的公司
|
|
48
|
+
function readFileConfig() {
|
|
49
|
+
try {
|
|
50
|
+
const parsed = JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf8'));
|
|
51
|
+
const companies = Array.isArray(parsed)
|
|
52
|
+
? parsed
|
|
53
|
+
: Array.isArray(parsed?.companies)
|
|
54
|
+
? parsed.companies
|
|
55
|
+
: null;
|
|
56
|
+
if (companies) {
|
|
57
|
+
return companies.find((item) => item && item.enable) || {};
|
|
58
|
+
}
|
|
59
|
+
return parsed || {};
|
|
60
|
+
} catch {
|
|
61
|
+
return {};
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// 配置文件优先:有启用公司就用配置,否则回落到环境变量
|
|
66
|
+
export function readActiveConfig() {
|
|
67
|
+
const envConfig = readEnvConfig();
|
|
68
|
+
if (isEnvOnly()) {
|
|
69
|
+
return envConfig;
|
|
70
|
+
}
|
|
71
|
+
const fileConfig = readFileConfig();
|
|
72
|
+
if (Object.keys(fileConfig).length > 0) {
|
|
73
|
+
return fileConfig;
|
|
74
|
+
}
|
|
75
|
+
return envConfig;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// 当前生效来源是否为环境变量
|
|
79
|
+
export function isEnvActive() {
|
|
80
|
+
if (isEnvOnly()) {
|
|
81
|
+
return hasEnvConfig();
|
|
82
|
+
}
|
|
83
|
+
return Object.keys(readFileConfig()).length === 0 && hasEnvConfig();
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function getEnvVarNames() {
|
|
87
|
+
return Object.values(ENV_VAR_NAMES);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// token-set 成功后把凭证写入环境变量:Windows 用 setx 写用户级变量,其他平台写 ~/.xbbcli/env.sh
|
|
91
|
+
export function persistEnvVars(values) {
|
|
92
|
+
const entries = Object.entries(ENV_VAR_NAMES)
|
|
93
|
+
.map(([field, name]) => [name, trim(values?.[field])])
|
|
94
|
+
.filter(([, value]) => value !== '');
|
|
95
|
+
if (!entries.length) {
|
|
96
|
+
return { stored: 'none', file: '', failed: [] };
|
|
97
|
+
}
|
|
98
|
+
if (process.platform === 'win32') {
|
|
99
|
+
const failed = [];
|
|
100
|
+
for (const [name, value] of entries) {
|
|
101
|
+
const result = spawnSync('setx', [name, value], { encoding: 'utf8', windowsHide: true });
|
|
102
|
+
if (result.error || result.status !== 0) failed.push(name);
|
|
103
|
+
}
|
|
104
|
+
if (failed.length) {
|
|
105
|
+
throw new Error(`写入用户环境变量失败:${failed.join(', ')}`);
|
|
106
|
+
}
|
|
107
|
+
return { stored: 'user', file: '', failed: [] };
|
|
108
|
+
}
|
|
109
|
+
const lines = entries.map(([name, value]) => `export ${name}='${value.replaceAll("'", "'\\''")}'`);
|
|
110
|
+
fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
111
|
+
fs.writeFileSync(ENV_FILE, `${lines.join('\n')}\n`, { encoding: 'utf8', mode: 0o600 });
|
|
112
|
+
return { stored: 'file', file: ENV_FILE, failed: [] };
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// auth-logout --env:清除 XBB_* 环境变量(Windows 删除用户级变量,其他平台清理 env.sh 里的 export 行)
|
|
116
|
+
export function clearEnvVars() {
|
|
117
|
+
const names = getEnvVarNames();
|
|
118
|
+
for (const name of names) {
|
|
119
|
+
delete process.env[name];
|
|
120
|
+
}
|
|
121
|
+
if (process.platform === 'win32') {
|
|
122
|
+
const failed = [];
|
|
123
|
+
let deleted = 0;
|
|
124
|
+
for (const name of names) {
|
|
125
|
+
const result = spawnSync('reg', ['delete', 'HKCU\\Environment', '/f', '/v', name], { encoding: 'utf8', windowsHide: true });
|
|
126
|
+
// 变量原本就不存在时 reg 返回非 0,这里只把进程无法启动视为失败
|
|
127
|
+
if (result.error) failed.push(name);
|
|
128
|
+
else if (result.status === 0) deleted += 1;
|
|
129
|
+
}
|
|
130
|
+
if (failed.length) {
|
|
131
|
+
throw new Error(`清除用户环境变量失败:${failed.join(', ')}`);
|
|
132
|
+
}
|
|
133
|
+
return { stored: deleted ? 'user' : 'none', file: '' };
|
|
134
|
+
}
|
|
135
|
+
if (!fs.existsSync(ENV_FILE)) {
|
|
136
|
+
return { stored: 'none', file: ENV_FILE };
|
|
137
|
+
}
|
|
138
|
+
const pattern = new RegExp('^\\s*export\\s+(' + names.join('|') + ')(\\s|=)');
|
|
139
|
+
const kept = fs.readFileSync(ENV_FILE, 'utf8').split(/\r?\n/).filter((line) => line.trim() !== '' && !pattern.test(line));
|
|
140
|
+
if (!kept.length) {
|
|
141
|
+
fs.rmSync(ENV_FILE, { force: true });
|
|
142
|
+
return { stored: 'removed', file: ENV_FILE };
|
|
143
|
+
}
|
|
144
|
+
fs.writeFileSync(ENV_FILE, `${kept.join('\n')}\n`, { encoding: 'utf8', mode: 0o600 });
|
|
145
|
+
return { stored: 'cleared', file: ENV_FILE };
|
|
146
|
+
}
|
|
@@ -0,0 +1,389 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import os from 'node:os';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { getRegistry } from './xbb-registry.js';
|
|
5
|
+
import { persistEnvVars } from './xbb-config.js';
|
|
6
|
+
|
|
7
|
+
// token-set 与 auth-login 共用的凭证落盘逻辑:写 config.env、表单缓存、命令映射、部门/员工缓存、环境变量
|
|
8
|
+
export const CONFIG_DIR = path.join(os.homedir(), '.xbbcli');
|
|
9
|
+
export const CONFIG_FILE = path.join(CONFIG_DIR, 'config.env');
|
|
10
|
+
export const FORMLIST_FILE_SUFFIX = '.formlist.json';
|
|
11
|
+
export const COMMAND_MAP_FILE_SUFFIX = '.command-map.md';
|
|
12
|
+
export const DEPARTMENT_USER_FILE_SUFFIX = '.department-user.json';
|
|
13
|
+
export const READ_ALL_PAGE_SIZE = 200;
|
|
14
|
+
export const READ_ALL_MAX_PAGES = 1000;
|
|
15
|
+
|
|
16
|
+
export function resolveBaseUrl(corpid) {
|
|
17
|
+
if (corpid.startsWith('ding') || corpid.includes('$$ding')) {
|
|
18
|
+
return 'https://proapi.xbongbong.com';
|
|
19
|
+
}
|
|
20
|
+
return 'https://appapi.xbongbong.com';
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function normalizeArg(value) {
|
|
24
|
+
return String(value || '').trim();
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function getFormlistFile(corpid) {
|
|
28
|
+
return path.join(CONFIG_DIR, `${corpid}${FORMLIST_FILE_SUFFIX}`);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function getCommandMapFile(corpid) {
|
|
32
|
+
return path.join(CONFIG_DIR, `${corpid}${COMMAND_MAP_FILE_SUFFIX}`);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function getDepartmentUserFile(corpid) {
|
|
36
|
+
return path.join(CONFIG_DIR, `${corpid}${DEPARTMENT_USER_FILE_SUFFIX}`);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export async function runCommandJson(name, kwargs) {
|
|
40
|
+
const command = getRegistry().get(`xbb/${name}`);
|
|
41
|
+
if (!command) throw new Error(`找不到命令:${name}`);
|
|
42
|
+
const rows = await command.func(kwargs);
|
|
43
|
+
if (!Array.isArray(rows)) throw new Error(`${name} 返回结果不是数组`);
|
|
44
|
+
return rows;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async function getFormlistRows(corpid, saasMark) {
|
|
48
|
+
const rows = await runCommandJson('form-list', { saasMark: String(saasMark) });
|
|
49
|
+
|
|
50
|
+
if (!Array.isArray(rows)) {
|
|
51
|
+
throw new Error(`form-list 返回结果不是数组:saasMark=${saasMark}`);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const errorRow = rows.find((item) => item && item.code);
|
|
55
|
+
if (errorRow) {
|
|
56
|
+
throw new Error(`form-list 获取失败(saasMark=${saasMark}):${errorRow.code} ${errorRow.msg || ''}`.trim());
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return rows;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// 先按 resetToken=0 查询是否已存在个人 token,返回空串再按 resetToken=1 重新生成
|
|
63
|
+
async function requestPersonalToken(corpid, token, userId, resetToken) {
|
|
64
|
+
const rows = await runCommandJson('token-generate', {
|
|
65
|
+
checkUserId: userId,
|
|
66
|
+
resetToken: String(resetToken),
|
|
67
|
+
token,
|
|
68
|
+
corpid,
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
if (!Array.isArray(rows)) {
|
|
72
|
+
throw new Error('token-generate 返回结果不是数组');
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const errorRow = rows.find((item) => item && String(item.code ?? '') !== '' && Number(item.code) !== 1);
|
|
76
|
+
if (errorRow) {
|
|
77
|
+
throw new Error(`token-generate 获取个人 token 失败:${errorRow.code} ${errorRow.msg || ''}`.trim());
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return String(rows[0]?.token || '').trim();
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export async function getPersonalToken(corpid, token, userId) {
|
|
84
|
+
const existingToken = await requestPersonalToken(corpid, token, userId, 0);
|
|
85
|
+
|
|
86
|
+
if (existingToken) {
|
|
87
|
+
if (!existingToken.startsWith('user_')) {
|
|
88
|
+
throw new Error('token-generate 未返回有效的个人 token');
|
|
89
|
+
}
|
|
90
|
+
return existingToken;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const personalToken = await requestPersonalToken(corpid, token, userId, 1);
|
|
94
|
+
if (!personalToken.startsWith('user_')) {
|
|
95
|
+
throw new Error('token-generate 未返回有效的个人 token');
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return personalToken;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
async function writeFormlistFile(corpid) {
|
|
102
|
+
const customForms = await getFormlistRows(corpid, 2);
|
|
103
|
+
const systemForms = await getFormlistRows(corpid, 1);
|
|
104
|
+
const formlistFile = getFormlistFile(corpid);
|
|
105
|
+
const mergedRows = [...customForms, ...systemForms];
|
|
106
|
+
|
|
107
|
+
fs.writeFileSync(formlistFile, JSON.stringify(mergedRows, null, 2) + '\n', 'utf8');
|
|
108
|
+
return formlistFile;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
async function fetchAllRows(name, pageSize) {
|
|
112
|
+
const rows = [];
|
|
113
|
+
for (let page = 1; page <= READ_ALL_MAX_PAGES; page += 1) {
|
|
114
|
+
const pageRows = await runCommandJson(name, { page: String(page), pageSize: String(pageSize) });
|
|
115
|
+
const errorRow = pageRows.find((item) => item && String(item.code ?? '') !== '');
|
|
116
|
+
if (errorRow) {
|
|
117
|
+
if (String(errorRow.code) === 'NO_DATA') {
|
|
118
|
+
return rows;
|
|
119
|
+
}
|
|
120
|
+
throw new Error(`${name} 获取失败(page=${page}):${errorRow.code} ${errorRow.msg || ''}`.trim());
|
|
121
|
+
}
|
|
122
|
+
rows.push(...pageRows);
|
|
123
|
+
}
|
|
124
|
+
throw new Error(`${name} 分页超过 ${READ_ALL_MAX_PAGES} 页,未获取到完整数据`);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function parseDepartmentListField(value) {
|
|
128
|
+
if (Array.isArray(value)) {
|
|
129
|
+
return value;
|
|
130
|
+
}
|
|
131
|
+
const text = String(value ?? '').trim();
|
|
132
|
+
if (!text) {
|
|
133
|
+
return [];
|
|
134
|
+
}
|
|
135
|
+
try {
|
|
136
|
+
const parsed = JSON.parse(text);
|
|
137
|
+
return Array.isArray(parsed) ? parsed : text;
|
|
138
|
+
} catch {
|
|
139
|
+
return text;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function toDepartmentEntry(row) {
|
|
144
|
+
return {
|
|
145
|
+
id: row.id ?? '',
|
|
146
|
+
name: row.name ?? '',
|
|
147
|
+
parentId: row.parentId ?? '',
|
|
148
|
+
depIdRouter: row.depIdRouter ?? '',
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function toUserEntry(row) {
|
|
153
|
+
return {
|
|
154
|
+
userId: row.userId ?? '',
|
|
155
|
+
name: row.name ?? '',
|
|
156
|
+
position: row.position ?? '',
|
|
157
|
+
jobnumber: row.jobnumber ?? '',
|
|
158
|
+
departmentList: parseDepartmentListField(row.departmentList),
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
async function writeDepartmentUserFile(corpid, userId) {
|
|
163
|
+
const departmentRows = await fetchAllRows('department-list', READ_ALL_PAGE_SIZE);
|
|
164
|
+
const userRows = await fetchAllRows('user-list', READ_ALL_PAGE_SIZE);
|
|
165
|
+
const payload = {
|
|
166
|
+
corpid,
|
|
167
|
+
updatedAt: new Date().toISOString(),
|
|
168
|
+
pageSize: READ_ALL_PAGE_SIZE,
|
|
169
|
+
departmentCount: departmentRows.length,
|
|
170
|
+
userCount: userRows.length,
|
|
171
|
+
departments: departmentRows.map(toDepartmentEntry),
|
|
172
|
+
users: userRows.map(toUserEntry),
|
|
173
|
+
};
|
|
174
|
+
const file = getDepartmentUserFile(corpid);
|
|
175
|
+
fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
176
|
+
fs.writeFileSync(file, JSON.stringify(payload, null, 2) + '\n', 'utf8');
|
|
177
|
+
return { file, corpName: getCorpName(departmentRows), userName: getUserName(userRows, userId) };
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function getCorpName(departmentRows) {
|
|
181
|
+
const root = departmentRows.find((row) => Number(row.id) === 1);
|
|
182
|
+
return root ? String(root.name || '').trim() : '';
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function getUserName(userRows, userId) {
|
|
186
|
+
const target = String(userId || '').trim();
|
|
187
|
+
if (!target) {
|
|
188
|
+
return '';
|
|
189
|
+
}
|
|
190
|
+
const exact = userRows.find((row) => String(row.userId || '').trim() === target);
|
|
191
|
+
const matched = exact || userRows.find((row) => String(row.userId || '').trim().toLowerCase() === target.toLowerCase());
|
|
192
|
+
return matched ? String(matched.name || '').trim() : '';
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
export function writeCompanyProfile(corpid, profile) {
|
|
196
|
+
const fields = Object.entries(profile).filter(([, value]) => String(value || '').trim() !== '');
|
|
197
|
+
if (!fields.length) {
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
const patch = Object.fromEntries(fields);
|
|
201
|
+
const companies = readCompanies().map((item) => (
|
|
202
|
+
String(item.corpid || '').trim() === corpid ? { ...item, ...patch } : item
|
|
203
|
+
));
|
|
204
|
+
fs.writeFileSync(CONFIG_FILE, JSON.stringify(companies, null, 2) + '\n', 'utf8');
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function normalizeBusinessType(value) {
|
|
208
|
+
return String(value).trim().replace(/[))\]】,,;;::。!?!?]+$/g, '');
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function getCommandBusinessType(command) {
|
|
212
|
+
if (command.businessType !== undefined && command.businessType !== null) {
|
|
213
|
+
return normalizeBusinessType(command.businessType);
|
|
214
|
+
}
|
|
215
|
+
const description = String(command.description || '');
|
|
216
|
+
const match = description.match(/businessType\s*:\s*(\d+)/i);
|
|
217
|
+
return match ? normalizeBusinessType(match[1]) : '';
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function escapeCell(value) {
|
|
221
|
+
return String(value ?? '').replace(/\|/g, '\\|').replace(/\r?\n/g, ' ');
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function optionDescription(option) {
|
|
225
|
+
return `--${option.name}: ${option.help || ''}`;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function writeCommandMap(corpid) {
|
|
229
|
+
const formFile = getFormlistFile(corpid);
|
|
230
|
+
if (!fs.existsSync(formFile)) {
|
|
231
|
+
throw new Error(`Form cache not found: ${formFile}`);
|
|
232
|
+
}
|
|
233
|
+
const raw = fs.readFileSync(formFile, 'utf8').replace(/^\uFEFF/, '');
|
|
234
|
+
const data = JSON.parse(raw);
|
|
235
|
+
const forms = Array.isArray(data) ? data : data.data || [];
|
|
236
|
+
|
|
237
|
+
const commands = [...getRegistry().values()]
|
|
238
|
+
.filter((command) => command.site === 'xbb')
|
|
239
|
+
.map((command) => ({
|
|
240
|
+
...command,
|
|
241
|
+
command_options: command.args || [],
|
|
242
|
+
}));
|
|
243
|
+
|
|
244
|
+
const lines = [
|
|
245
|
+
'# XBB 命令映射',
|
|
246
|
+
'',
|
|
247
|
+
`来源:xbb --help;表单缓存:${path.basename(formFile)}`,
|
|
248
|
+
'',
|
|
249
|
+
'| 命令 | 命令名称 | formId | businessType | 字段说明 |',
|
|
250
|
+
'| --- | --- | --- | --- | --- |',
|
|
251
|
+
];
|
|
252
|
+
|
|
253
|
+
for (const command of commands) {
|
|
254
|
+
const commandBusinessType = getCommandBusinessType(command);
|
|
255
|
+
const matchedForms = forms.filter((form) => String(form.businessType ?? '').trim() === commandBusinessType);
|
|
256
|
+
const formIds = [...new Set(matchedForms.map((form) => form.formId).filter(Boolean))];
|
|
257
|
+
const businessTypes = [...new Set(matchedForms.map((form) => form.businessType).filter(Boolean))];
|
|
258
|
+
const options = (command.command_options || []).map(optionDescription).join('<br>');
|
|
259
|
+
lines.push(
|
|
260
|
+
`| ${escapeCell(command.name)} | ${escapeCell(command.description)} | ${escapeCell(formIds.join(', '))} | ${escapeCell(businessTypes.join(', ') || commandBusinessType)} | ${escapeCell(options)} |`,
|
|
261
|
+
);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
const commandMapFile = getCommandMapFile(corpid);
|
|
265
|
+
fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
266
|
+
fs.writeFileSync(commandMapFile, `${lines.join('\n')}\n`, 'utf8');
|
|
267
|
+
return commandMapFile;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
export function createResult(status, message, corpid, baseurl, userId, formlistFile = '', commandMapFile = '', enable = '', companyCount = '', departmentUserFile = '', corpName = '', userName = '', envStored = '', envFile = '') {
|
|
271
|
+
return [{
|
|
272
|
+
status,
|
|
273
|
+
message,
|
|
274
|
+
configFile: CONFIG_FILE,
|
|
275
|
+
corpid,
|
|
276
|
+
baseurl,
|
|
277
|
+
userId,
|
|
278
|
+
enable,
|
|
279
|
+
companyCount,
|
|
280
|
+
formlistFile,
|
|
281
|
+
commandMapFile,
|
|
282
|
+
departmentUserFile,
|
|
283
|
+
corpName,
|
|
284
|
+
userName,
|
|
285
|
+
envStored,
|
|
286
|
+
envFile,
|
|
287
|
+
}];
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
export function readCompanies() {
|
|
291
|
+
try {
|
|
292
|
+
const parsed = JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf8'));
|
|
293
|
+
return normalizeCompanies(parsed);
|
|
294
|
+
} catch {
|
|
295
|
+
return [];
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
function normalizeCompanies(parsed) {
|
|
300
|
+
if (Array.isArray(parsed)) {
|
|
301
|
+
return parsed.filter((item) => item && typeof item === 'object');
|
|
302
|
+
}
|
|
303
|
+
if (parsed && Array.isArray(parsed.companies)) {
|
|
304
|
+
return parsed.companies.filter((item) => item && typeof item === 'object');
|
|
305
|
+
}
|
|
306
|
+
if (parsed && parsed.corpid) {
|
|
307
|
+
const { corpid, token, baseurl, userId } = parsed;
|
|
308
|
+
return [{ corpid, token, baseurl, userId, enable: true }];
|
|
309
|
+
}
|
|
310
|
+
return [];
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
export function upsertCompany(companies, entry) {
|
|
314
|
+
const index = companies.findIndex((item) => String(item.corpid || '').trim() === entry.corpid);
|
|
315
|
+
const next = companies.map((item) => ({ ...item, enable: false }));
|
|
316
|
+
if (index === -1) {
|
|
317
|
+
next.push({ ...entry, enable: true });
|
|
318
|
+
} else {
|
|
319
|
+
next[index] = { ...next[index], ...entry, enable: true };
|
|
320
|
+
}
|
|
321
|
+
return next;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// token-set / auth-login 共用:保存一家公司的凭证并刷新本地缓存,返回与 token-set 相同结构的行
|
|
325
|
+
export async function saveCompanyCredentials(kwargs) {
|
|
326
|
+
const corpid = normalizeArg(kwargs.corpid);
|
|
327
|
+
const token = normalizeArg(kwargs.token);
|
|
328
|
+
const userId = normalizeArg(kwargs.userId);
|
|
329
|
+
|
|
330
|
+
if (!corpid) {
|
|
331
|
+
return createResult('error', '缺少 --corpid', '', '', '');
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
if (!token) {
|
|
335
|
+
return createResult('error', '缺少 --token', corpid, '', '');
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
if (!userId) {
|
|
339
|
+
return createResult('error', '缺少 --userId', corpid, '', '');
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
let personalToken = token;
|
|
343
|
+
if (!token.startsWith('user_')) {
|
|
344
|
+
try {
|
|
345
|
+
personalToken = await getPersonalToken(corpid, token, userId);
|
|
346
|
+
} catch (error) {
|
|
347
|
+
return createResult('error', `生成个人 token 失败:${error.message}`, corpid, '', userId);
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
const baseurl = resolveBaseUrl(corpid);
|
|
352
|
+
const companies = upsertCompany(readCompanies(), { corpid, token: personalToken, baseurl, userId });
|
|
353
|
+
|
|
354
|
+
fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
355
|
+
fs.writeFileSync(CONFIG_FILE, JSON.stringify(companies, null, 2) + '\n', 'utf8');
|
|
356
|
+
|
|
357
|
+
try {
|
|
358
|
+
const formlistFile = await writeFormlistFile(corpid);
|
|
359
|
+
const commandMapFile = writeCommandMap(corpid);
|
|
360
|
+
const { file: departmentUserFile, corpName, userName } = await writeDepartmentUserFile(corpid, userId);
|
|
361
|
+
writeCompanyProfile(corpid, { corpName, userName });
|
|
362
|
+
let envStored = 'skipped';
|
|
363
|
+
let envFile = '';
|
|
364
|
+
if (!kwargs.noEnv) {
|
|
365
|
+
try {
|
|
366
|
+
const envResult = persistEnvVars({ corpid, token: personalToken, baseurl, userId, corpName, userName });
|
|
367
|
+
envStored = envResult.stored;
|
|
368
|
+
envFile = envResult.file;
|
|
369
|
+
} catch (error) {
|
|
370
|
+
envStored = 'failed';
|
|
371
|
+
envFile = String(error.message || error);
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
return createResult('ok', '已保存 corpid、token、baseurl、userId,并同步表单模板缓存、命令映射文件与部门/员工缓存', corpid, baseurl, userId, formlistFile, commandMapFile, true, companies.length, departmentUserFile, corpName, userName, envStored, envFile);
|
|
375
|
+
} catch (error) {
|
|
376
|
+
return createResult(
|
|
377
|
+
'partial',
|
|
378
|
+
`已保存 corpid、token、baseurl、userId,但同步缓存失败:${error.message}`,
|
|
379
|
+
corpid,
|
|
380
|
+
baseurl,
|
|
381
|
+
userId,
|
|
382
|
+
getFormlistFile(corpid),
|
|
383
|
+
'',
|
|
384
|
+
true,
|
|
385
|
+
companies.length,
|
|
386
|
+
'',
|
|
387
|
+
);
|
|
388
|
+
}
|
|
389
|
+
}
|
package/version.js
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import { cli, Strategy } from './xbb-registry.js';
|
|
2
|
-
|
|
3
|
-
const VERSION = 'v1.6';
|
|
4
|
-
|
|
5
|
-
cli({
|
|
6
|
-
site: 'xbb',
|
|
7
|
-
name: 'version',
|
|
8
|
-
description: '查看销帮帮插件版本号',
|
|
9
|
-
strategy: Strategy.PUBLIC,
|
|
10
|
-
access: 'read',
|
|
11
|
-
browser: false,
|
|
12
|
-
domain: 'proapi.xbongbong.com',
|
|
13
|
-
args: [],
|
|
14
|
-
columns: ['version'],
|
|
15
|
-
func: async function () {
|
|
16
|
-
return [{ version: VERSION }];
|
|
17
|
-
},
|
|
18
|
-
});
|