sks-maximo-utils 1.0.5 → 1.0.7

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/CLI.md ADDED
@@ -0,0 +1,226 @@
1
+ # CLI 命令使用说明
2
+
3
+ ## 命令概览
4
+
5
+ | 命令 | 说明 |
6
+ |------|------|
7
+ | `sks-maximo` | 检查配置文件并打开配置目录 |
8
+ | `sks-maximo <env>` | 加载指定环境配置 |
9
+ | `sks-maximo call <scriptName>` | 调用通用脚本接口 |
10
+ | `sks-maximo import-object <fileName>` | 导入 Maximo 对象配置 |
11
+ | `sks-maximo import-appinfo <fileName>` | 导入应用信息配置 |
12
+ | `sks-maximo import-presentation <fileName>` | 导入应用XML配置 |
13
+ | `sks-maximo import-script <fileName>` | 导入自动化脚本 |
14
+ | `sks-maximo import-domain <fileName>` | 导入域配置 |
15
+
16
+ ---
17
+
18
+ ## 1. 打开配置目录
19
+
20
+ ```bash
21
+ sks-maximo
22
+ ```
23
+
24
+ **功能:**
25
+ - 检查 `~/.sks/nodeutils/config.json` 是否存在
26
+ - 不存在则复制 `sample.config.json` 创建
27
+ - 自动打开配置文件所在目录
28
+
29
+ ---
30
+
31
+ ## 2. 加载环境配置
32
+
33
+ ```bash
34
+ sks-maximo local # 使用本地环境配置
35
+ sks-maximo dev # 使用开发环境配置
36
+ sks-maximo hd # 使用测试环境配置
37
+ ```
38
+
39
+ **功能:**
40
+ - 加载指定环境的配置到全局
41
+ - 后续所有 API 调用使用该环境的 baseUrl、apiKey 等配置
42
+
43
+ ---
44
+
45
+ ## 3. 导入命令
46
+
47
+ 所有导入命令均支持 `-e` / `--env` 指定环境和 `-l` / `--logname` 指定日志名。
48
+
49
+ ### 3.1 导入对象配置
50
+
51
+ ```bash
52
+ sks-maximo import-object <fileName> [-e env] [-l logname]
53
+ ```
54
+
55
+ **说明:** 导入 Maximo 对象配置(JSON/XML)
56
+
57
+ | 参数 | 说明 | 必填 |
58
+ |------|------|------|
59
+ | `<fileName>` | JSON 或 XML 配置文件路径 | 是 |
60
+ | `-e` / `--env` | 指定环境(默认 local) | 否 |
61
+ | `-l` / `--logname` | 日志打印名称 | 否 |
62
+
63
+ ---
64
+
65
+ ### 3.2 导入应用信息
66
+
67
+ ```bash
68
+ sks-maximo import-appinfo <fileName> [-e env] [-l logname]
69
+ ```
70
+
71
+ **说明:** 导入 Maximo 应用信息配置(JSON)
72
+
73
+ | 参数 | 说明 | 必填 |
74
+ |------|------|------|
75
+ | `<fileName>` | 应用信息 JSON 文件路径 | 是 |
76
+ | `-e` / `--env` | 指定环境(默认 local) | 否 |
77
+ | `-l` / `--logname` | 日志打印名称 | 否 |
78
+
79
+ ---
80
+
81
+ ### 3.3 导入应用XML配置
82
+
83
+ ```bash
84
+ sks-maximo import-presentation <fileName> [-e env] [-l logname]
85
+ ```
86
+
87
+ **说明:** 导入 Maximo 应用 XML 配置
88
+
89
+ | 参数 | 说明 | 必填 |
90
+ |------|------|------|
91
+ | `<fileName>` | XML 配置文件路径 | 是 |
92
+ | `-e` / `--env` | 指定环境(默认 local) | 否 |
93
+ | `-l` / `--logname` | 日志打印名称 | 否 |
94
+
95
+ ---
96
+
97
+ ### 3.4 导入自动化脚本
98
+
99
+ ```bash
100
+ sks-maximo import-script <fileName> [-e env] [-l logname]
101
+ ```
102
+
103
+ **说明:** 导入 Maximo 自动化脚本。`<fileName>` 可以是 JSON 配置文件路径,也可以是 `.js`/`.py` 脚本文件路径(会自动找同目录下的同名 JSON 配置文件)
104
+
105
+ | 参数 | 说明 | 必填 |
106
+ |------|------|------|
107
+ | `<fileName>` | JSON 配置或脚本文件路径 | 是 |
108
+ | `-e` / `--env` | 指定环境(默认 local) | 否 |
109
+ | `-l` / `--logname` | 日志打印名称 | 否 |
110
+
111
+ ---
112
+
113
+ ### 3.5 导入域配置
114
+
115
+ ```bash
116
+ sks-maximo import-domain <fileName> [-e env] [-l logname]
117
+ ```
118
+
119
+ **说明:** 导入 Maximo 域配置
120
+
121
+ | 参数 | 说明 | 必填 |
122
+ |------|------|------|
123
+ | `<fileName>` | 域 JSON 文件路径 | 是 |
124
+ | `-e` / `--env` | 指定环境(默认 local) | 否 |
125
+ | `-l` / `--logname` | 日志打印名称 | 否 |
126
+
127
+ ---
128
+
129
+ ## 4. 调用通用脚本接口
130
+
131
+ ```bash
132
+ sks-maximo call <scriptName> [--file <fileName>] [--params <params>] [--logname <logname>]
133
+ ```
134
+
135
+ **参数说明:**
136
+
137
+ | 参数 | 简写 | 说明 | 必填 |
138
+ |------|------|------|------|
139
+ | `<scriptName>` | - | Maximo 脚本接口名称 | 是 |
140
+ | `--file` | `-f` | 请求体文件路径(JSON/XML等) | 否 |
141
+ | `--params` | `-p` | URL 查询参数(JSON格式) | 否 |
142
+ | `--logname` | `-l` | 日志名称 | 否 |
143
+
144
+ ### 4.1 GET 请求(无请求体)
145
+
146
+ ```bash
147
+ sks-maximo call SHARPTREE.AUTOSCRIPT.LIBRARY --params '{"name":"TEST"}'
148
+ ```
149
+
150
+ ### 4.2 POST 请求(带请求体文件)
151
+
152
+ ```bash
153
+ # JSON 请求体
154
+ sks-maximo call SKS_DEPLOY_AUTOKEY --file data/autokey.json
155
+
156
+ # XML 请求体
157
+ sks-maximo call SHARPTREE.AUTOSCRIPT.SCREENS --file screens/test.xml
158
+
159
+ # 同时带参数和请求体
160
+ sks-maximo call SKS_IMP_MAXAPPINFO --file appinfo.json --params '{"debug":true}' --logname "导入应用信息"
161
+ ```
162
+
163
+ ### 4.3 请求体文件处理
164
+
165
+ - **`.json` 文件**:自动解析为 JSON 对象作为请求体
166
+ - **其他文件**:读取文件内容作为字符串请求体
167
+
168
+ ---
169
+
170
+ ## 配置文件说明
171
+
172
+ 配置文件位置:`~/.sks/nodeutils/config.json`
173
+
174
+ ```json
175
+ {
176
+ "logLevel": "INFO",
177
+ "langcode": "zh",
178
+ "envs": {
179
+ "local": {
180
+ "baseUrl": "http://127.0.0.1:9080/maximo",
181
+ "apiKey": "your-api-key",
182
+ "maxauth": "",
183
+ "authType": "apiKey",
184
+ "langcode": "zh"
185
+ },
186
+ "dev": {
187
+ "baseUrl": "https://mdev/maximo",
188
+ "apiKey": "your-api-key",
189
+ "maxauth": "",
190
+ "authType": "apiKey"
191
+ }
192
+ }
193
+ }
194
+ ```
195
+
196
+ ---
197
+
198
+ ## 示例
199
+
200
+ ### 示例 1:查询脚本库
201
+
202
+ ```bash
203
+ sks-maximo call SHARPTREE.AUTOSCRIPT.LIBRARY --params '{"name":"MY_SCRIPT"}'
204
+ ```
205
+
206
+ ### 示例 2:部署自动编码
207
+
208
+ ```bash
209
+ sks-maximo call SKS_DEPLOY_AUTOKEY --file autokey/test.json --logname "部署自动编码"
210
+ ```
211
+
212
+ ### 示例 3:导入应用信息
213
+
214
+ ```bash
215
+ sks-maximo call SKS_IMP_MAXAPPINFO --file appinfo/APP001.json
216
+ ```
217
+
218
+ ---
219
+
220
+ ## 帮助信息
221
+
222
+ ```bash
223
+ sks-maximo --help # 显示帮助
224
+ sks-maximo -h # 显示帮助
225
+ sks-maximo -v # 显示版本号
226
+ ```
package/README.md CHANGED
@@ -11,7 +11,7 @@ npm install -g shoukaiseki-maximo-utils
11
11
 
12
12
  ### 项目中安装(引用使用)
13
13
  ```bash
14
- npm install shoukaiseki-maximo-utils
14
+ npm install sks-maximo-utils
15
15
  ```
16
16
 
17
17
  ## 使用方式
@@ -40,9 +40,11 @@ import {
40
40
  importMaxAutoKey,
41
41
  importMaxScript,
42
42
  importMaxAppInfo,
43
+ callMaxScript,
44
+ fileUtils,
43
45
  loadConfig,
44
46
  logger
45
- } from 'shoukaiseki-maximo-utils';
47
+ } from 'sks-maximo-utils';
46
48
 
47
49
  // 加载配置(可选,默认 local)
48
50
  loadConfig('local');
@@ -66,6 +68,16 @@ importMaxScript({ fileName: "scripts/TEST.json" });
66
68
 
67
69
  // 导入应用信息配置
68
70
  importMaxAppInfo({ fileName: "MAXAPPINFO/appinfo.json", logname: "应用信息" });
71
+
72
+ // 通用脚本接口调用(GET)
73
+ callMaxScript({ apiScriptName: "SHARPTREE.AUTOSCRIPT.LIBRARY", params: { name: "test" }, logname: "查询脚本库" });
74
+
75
+ // 通用脚本接口调用(POST,读取文件内容作为请求体)
76
+ callMaxScript({ apiScriptName: "SKS_DEPLOY_AUTOKEY", fileName: "data/autokey.json", logname: "部署自动编码" });
77
+
78
+ // 文件工具
79
+ fileUtils.listFiles('scripts', '.json');
80
+ fileUtils.readFileContent('test.txt');
69
81
  ```
70
82
 
71
83
  ## 配置文件
@@ -126,11 +138,15 @@ importMaxAppInfo({ fileName: "MAXAPPINFO/appinfo.json", logname: "应用信息"
126
138
 
127
139
  | 方法 | 说明 |
128
140
  |------|------|
141
+ | callMaxScript | 通用脚本接口调用(GET/POST) |
129
142
  | loadConfig(env) | 加载指定环境配置 |
130
143
  | saveScriptHistory(options) | 保存脚本历史记录 |
131
144
  | readFileContent(fileName) | 读取文件内容 |
132
145
  | readJsonFile(fileName) | 读取并解析 JSON 文件 |
133
146
  | fileExists(fileName) | 检查文件是否存在 |
147
+ | fileUtils.listFiles(dirName, extFilter, recursive) | 列出目录文件 |
148
+ | fileUtils.readFileContent(fileName) | 读取文件内容 |
149
+ | fileUtils.readJsonFile(fileName) | 读取并解析 JSON 文件 |
134
150
 
135
151
  ### 日志文件
136
152
 
@@ -148,7 +164,7 @@ npm install
148
164
  npm link
149
165
 
150
166
  # 在其他项目中链接测试
151
- npm link shoukaiseki-maximo-utils
167
+ npm link sks-maximo-utils
152
168
  ```
153
169
 
154
170
  ## License
package/cli.js CHANGED
@@ -1,29 +1,67 @@
1
1
  #!/usr/bin/env node
2
2
  import { exec } from 'child_process';
3
+ import { readFileSync } from 'fs';
4
+ import { fileURLToPath, URL } from 'url';
3
5
  import { checkAndCreateConfig, getConfigDir, loadConfig } from './nodeutils/config.js';
6
+ import { callMaxScript, importMaxObject, importMaxPresentation, importMaxDomain, importMaxAutoKey, importMaxScript, importMaxAppInfo } from './nodeutils/maximoTools.js';
7
+
8
+ const __filename = fileURLToPath(import.meta.url);
9
+ const __dirname = new URL('.', import.meta.url).pathname;
10
+ const packageJson = JSON.parse(readFileSync(new URL('./package.json', import.meta.url), 'utf-8'));
4
11
 
5
12
  const args = process.argv.slice(2);
6
- const env = args[0];
7
13
 
8
- if (!env || ['-h', '--help', '-?', '--version', '-v'].includes(env)) {
9
- console.log(`sks-maximo v${require('./package.json').version}`);
14
+ // 解析全局选项(--env)
15
+ let globalEnv = 'local';
16
+ const filteredArgs = [];
17
+ for (let i = 0; i < args.length; i++) {
18
+ if (args[i] === '--env' || args[i] === '-e') {
19
+ globalEnv = args[i + 1];
20
+ i++; // 跳过值
21
+ } else {
22
+ filteredArgs.push(args[i]);
23
+ }
24
+ }
25
+
26
+ const cmd = filteredArgs[0];
27
+ const env = filteredArgs[0];
28
+
29
+ if (!cmd || ['-h', '--help', '-?', '--version', '-v'].includes(cmd)) {
30
+ console.log(`sks-maximo v${packageJson.version}`);
10
31
  console.log('');
11
32
  console.log('用法:');
12
33
  console.log(' sks-maximo # 检查配置文件并打开配置目录');
13
34
  console.log(' sks-maximo <env> # 加载指定环境配置');
35
+ console.log(' sks-maximo call <scriptName> [options] # 调用脚本接口');
36
+ console.log(' sks-maximo import-object <fileName> # 导入 Maximo 对象配置');
37
+ console.log(' sks-maximo import-appinfo <fileName> # 导入应用信息配置');
38
+ console.log(' sks-maximo import-presentation <fileName> # 导入应用XML配置');
39
+ console.log(' sks-maximo import-script <fileName> # 导入自动化脚本');
40
+ console.log(' sks-maximo import-domain <fileName> # 导入域配置');
14
41
  console.log('');
15
42
  console.log('环境:');
16
43
  console.log(' local # 使用本地环境配置');
17
44
  console.log(' dev # 使用开发环境配置');
18
45
  console.log(' hd # 使用测试环境配置');
19
46
  console.log('');
47
+ console.log('命令:');
48
+ console.log(' call # 调用通用脚本接口');
49
+ console.log(' import-object # 导入 Maximo 对象配置');
50
+ console.log(' import-appinfo # 导入应用信息配置');
51
+ console.log(' import-presentation # 导入应用XML配置');
52
+ console.log(' import-script # 导入自动化脚本');
53
+ console.log(' import-domain # 导入域配置');
54
+ console.log('');
20
55
  console.log('选项:');
21
56
  console.log(' -h, --help # 显示帮助信息');
22
57
  console.log(' -v, --version # 显示版本信息');
58
+ console.log(' --env, -e <env> # 指定环境(默认 local)');
59
+ console.log(' --file <fileName> # 请求体文件路径(JSON)');
60
+ console.log(' --params <params> # URL参数(JSON格式)');
23
61
  console.log('');
24
62
  console.log('配置文件位置: ~/.sks/nodeutils/config.json');
25
63
 
26
- if (!env) {
64
+ if (!cmd) {
27
65
  const configPath = checkAndCreateConfig();
28
66
  const configDir = getConfigDir();
29
67
 
@@ -48,8 +86,195 @@ if (!env || ['-h', '--help', '-?', '--version', '-v'].includes(env)) {
48
86
  }
49
87
  });
50
88
  }
89
+ } else if (cmd === 'call') {
90
+ const scriptName = args[1];
91
+
92
+ if (!scriptName) {
93
+ console.error('错误: 请指定脚本名称');
94
+ console.log('用法: sks-maximo call <scriptName> [--file <fileName>] [--params <params>]');
95
+ process.exit(1);
96
+ }
97
+
98
+ let fileName = null;
99
+ let params = {};
100
+ let logname = null;
101
+
102
+ for (let i = 2; i < args.length; i++) {
103
+ if (args[i] === '--file' || args[i] === '-f') {
104
+ fileName = args[i + 1];
105
+ i++;
106
+ } else if (args[i] === '--params' || args[i] === '-p') {
107
+ try {
108
+ params = JSON.parse(args[i + 1]);
109
+ } catch (e) {
110
+ console.error(`参数解析失败: ${e.message}`);
111
+ process.exit(1);
112
+ }
113
+ i++;
114
+ } else if (args[i] === '--logname' || args[i] === '-l') {
115
+ logname = args[i + 1];
116
+ i++;
117
+ }
118
+ }
119
+
120
+ loadConfig(globalEnv);
121
+
122
+ (async () => {
123
+ try {
124
+ const response = await callMaxScript({ apiScriptName: scriptName, fileName, params, logname });
125
+ if (response) {
126
+ console.log('响应:', JSON.stringify(response, null, 2));
127
+ } else {
128
+ console.error('调用失败');
129
+ process.exit(1);
130
+ }
131
+ } catch (error) {
132
+ console.error('调用出错:', error.message);
133
+ process.exit(1);
134
+ }
135
+ })();
136
+ } else if (cmd === 'import-object') {
137
+ const fileName = filteredArgs[1];
138
+ if (!fileName) {
139
+ console.error('错误: 请指定 JSON 文件路径');
140
+ console.log('用法: sks-maximo import-object <fileName> [-e env]');
141
+ process.exit(1);
142
+ }
143
+ loadConfig(globalEnv);
144
+ (async () => {
145
+ try {
146
+ const result = await importMaxObject({ fileName, logname: fileName });
147
+ if (result) {
148
+ console.log('导入成功');
149
+ } else {
150
+ console.error('导入失败');
151
+ process.exit(1);
152
+ }
153
+ } catch (error) {
154
+ console.error('导入出错:', error.message);
155
+ process.exit(1);
156
+ }
157
+ })();
158
+ } else if (cmd === 'import-appinfo') {
159
+ const fileName = filteredArgs[1];
160
+ if (!fileName) {
161
+ console.error('错误: 请指定 JSON 文件路径');
162
+ console.log('用法: sks-maximo import-appinfo <fileName> [-e env] [-l logname]');
163
+ process.exit(1);
164
+ }
165
+ let logname = null;
166
+ for (let i = 2; i < filteredArgs.length; i++) {
167
+ if (filteredArgs[i] === '--logname' || filteredArgs[i] === '-l') {
168
+ logname = filteredArgs[i + 1];
169
+ i++;
170
+ }
171
+ }
172
+ loadConfig(globalEnv);
173
+ (async () => {
174
+ try {
175
+ const result = await importMaxAppInfo({ fileName, logname: logname || fileName });
176
+ if (result) {
177
+ console.log('导入成功');
178
+ } else {
179
+ console.error('导入失败');
180
+ process.exit(1);
181
+ }
182
+ } catch (error) {
183
+ console.error('导入出错:', error.message);
184
+ process.exit(1);
185
+ }
186
+ })();
187
+ } else if (cmd === 'import-presentation') {
188
+ const fileName = filteredArgs[1];
189
+ if (!fileName) {
190
+ console.error('错误: 请指定 XML 文件路径');
191
+ console.log('用法: sks-maximo import-presentation <fileName> [-e env] [-l logname]');
192
+ process.exit(1);
193
+ }
194
+ let logname = null;
195
+ for (let i = 2; i < filteredArgs.length; i++) {
196
+ if (filteredArgs[i] === '--logname' || filteredArgs[i] === '-l') {
197
+ logname = filteredArgs[i + 1];
198
+ i++;
199
+ }
200
+ }
201
+ loadConfig(globalEnv);
202
+ (async () => {
203
+ try {
204
+ const result = await importMaxPresentation({ fileName, logname: logname || fileName });
205
+ if (result) {
206
+ console.log('导入成功');
207
+ } else {
208
+ console.error('导入失败');
209
+ process.exit(1);
210
+ }
211
+ } catch (error) {
212
+ console.error('导入出错:', error.message);
213
+ process.exit(1);
214
+ }
215
+ })();
216
+ } else if (cmd === 'import-script') {
217
+ const fileName = filteredArgs[1];
218
+ if (!fileName) {
219
+ console.error('错误: 请指定脚本 JSON 配置文件路径');
220
+ console.log('用法: sks-maximo import-script <fileName> [-e env] [-l logname]');
221
+ process.exit(1);
222
+ }
223
+ let logname = null;
224
+ for (let i = 2; i < filteredArgs.length; i++) {
225
+ if (filteredArgs[i] === '--logname' || filteredArgs[i] === '-l') {
226
+ logname = filteredArgs[i + 1];
227
+ i++;
228
+ }
229
+ }
230
+ loadConfig(globalEnv);
231
+ (async () => {
232
+ try {
233
+ const result = await importMaxScript({ fileName, logname: logname || fileName });
234
+ if (result) {
235
+ console.log('导入成功');
236
+ } else {
237
+ console.error('导入失败');
238
+ process.exit(1);
239
+ }
240
+ } catch (error) {
241
+ console.error('导入出错:', error.message);
242
+ process.exit(1);
243
+ }
244
+ })();
245
+ } else if (cmd === 'import-domain') {
246
+ const fileName = filteredArgs[1];
247
+ if (!fileName) {
248
+ console.error('错误: 请指定域 JSON 文件路径');
249
+ console.log('用法: sks-maximo import-domain <fileName> [-e env] [-l logname]');
250
+ process.exit(1);
251
+ }
252
+ let logname = null;
253
+ for (let i = 2; i < filteredArgs.length; i++) {
254
+ if (filteredArgs[i] === '--logname' || filteredArgs[i] === '-l') {
255
+ logname = filteredArgs[i + 1];
256
+ i++;
257
+ }
258
+ }
259
+ loadConfig(globalEnv);
260
+ (async () => {
261
+ try {
262
+ const result = await importMaxDomain({ fileName, logname: logname || fileName });
263
+ if (result) {
264
+ console.log('导入成功');
265
+ } else {
266
+ console.error('导入失败');
267
+ process.exit(1);
268
+ }
269
+ } catch (error) {
270
+ console.error('导入出错:', error.message);
271
+ process.exit(1);
272
+ }
273
+ })();
51
274
  } else {
52
- loadConfig(env);
275
+ console.error(`不支持的命令: ${cmd}`);
276
+ console.log('使用 sks-maximo --help 查看帮助信息');
277
+ process.exit(1);
53
278
  }
54
279
 
55
280
  export * from './nodeutils/maximoTools.js';
package/index.js CHANGED
@@ -2,6 +2,6 @@
2
2
  export { loadConfig, getConfig, getWorkDir } from './nodeutils/config.js';
3
3
  export { default as logger } from './nodeutils/logger.js';
4
4
  export { default as request } from './nodeutils/requestHttp.js';
5
- export { readFileContent, readJsonFile, readXmlFile, fileExists } from './nodeutils/fileUtils.js';
6
- export { importMaxObject, importMaxPresentation, importMaxDomain, importMaxAutoKey, importMaxScript, saveScriptHistory, importMaxAppInfo } from './nodeutils/maximoTools.js';
5
+ export * as fileUtils from './nodeutils/fileUtils.js';
6
+ export { importMaxObject, importMaxPresentation, importMaxDomain, importMaxAutoKey, importMaxScript, saveScriptHistory, importMaxAppInfo, callMaxScript, batchImport, printBatchStats } from './nodeutils/maximoTools.js';
7
7
  export { sendMockData, sendMockDataBatch, sendPOMockData, sendPOMockDataBatch, sendRlImportMockDataBatch } from './nodeutils/mockdataTools.js';
@@ -88,9 +88,70 @@ function fileExists(fileNameIn) {
88
88
  }
89
89
  }
90
90
 
91
+ /**
92
+ * 通用文件路径转换 - 将各种形式的文件路径转换为规范化后的实际可用路径
93
+ * @param {string} fileName - 文件名或路径(支持相对路径、绝对路径)
94
+ * @returns {string} 规范化后的绝对路径
95
+ */
96
+ function resolveFilePath(fileName) {
97
+ if (!fileName) return '';
98
+ // 统一分隔符
99
+ fileName = fileName.replace(/\\/g, '/');
100
+ // 绝对路径直接规范化返回
101
+ if (path.isAbsolute(fileName)) {
102
+ return path.normalize(fileName);
103
+ }
104
+ // 相对路径拼接工作目录
105
+ return path.normalize(path.join(getWorkDir(), fileName));
106
+ }
107
+
108
+ /**
109
+ * 列出目录中的文件
110
+ * @param {string} dirName - 目录名(相对或绝对路径)
111
+ * @param {string} [extFilter] - 文件扩展名过滤,如 '.json'、'.js',不传则返回所有文件
112
+ * @param {boolean} [recursive=true] - 是否递归检索子目录
113
+ * @returns {string[]} 文件全路径数组
114
+ */
115
+ function listFiles(dirName, extFilter, recursive = true) {
116
+ const dirPath = path.isAbsolute(dirName.replace(/\\/g, '/')) ? dirName : path.join(getWorkDir(), dirName);
117
+
118
+ try {
119
+ if (!fs.existsSync(dirPath)) {
120
+ console.error(`目录不存在: ${dirPath}`);
121
+ return [];
122
+ }
123
+
124
+ const results = [];
125
+
126
+ function scanDir(currentPath, baseRelative) {
127
+ const entries = fs.readdirSync(currentPath, { withFileTypes: true });
128
+ for (const entry of entries) {
129
+ const fullPath = path.join(currentPath, entry.name);
130
+ const relativePath = baseRelative ? path.join(baseRelative, entry.name) : entry.name;
131
+
132
+ if (entry.isFile()) {
133
+ if (!extFilter || entry.name.endsWith(extFilter)) {
134
+ results.push(fullPath);
135
+ }
136
+ } else if (entry.isDirectory() && recursive) {
137
+ scanDir(fullPath, relativePath);
138
+ }
139
+ }
140
+ }
141
+
142
+ scanDir(dirPath, '');
143
+ return results;
144
+ } catch (error) {
145
+ console.error(`列出目录文件失败: ${dirName}`, error.message);
146
+ return [];
147
+ }
148
+ }
149
+
91
150
  export {
92
151
  readFileContent,
93
152
  readJsonFile,
94
153
  readXmlFile,
95
- fileExists
154
+ fileExists,
155
+ listFiles,
156
+ resolveFilePath
96
157
  };
@@ -8,6 +8,17 @@ const LEVELS = {
8
8
  FATAL: 5
9
9
  };
10
10
 
11
+ // 颜色定义
12
+ const COLORS = {
13
+ TRACE: '\x1b[90m', // 灰色
14
+ DEBUG: '\x1b[36m', // 青色
15
+ INFO: '\x1b[32m', // 绿色
16
+ WARN: '\x1b[33m', // 黄色
17
+ ERROR: '\x1b[31m', // 红色
18
+ FATAL: '\x1b[1;31m' // 粗体红
19
+ };
20
+ const RESET = '\x1b[0m';
21
+
11
22
  class ConsoleLogger {
12
23
  constructor(level = 'INFO') {
13
24
  this.level = LEVELS[level] !== undefined ? LEVELS[level] : LEVELS.INFO;
@@ -27,7 +38,8 @@ class ConsoleLogger {
27
38
  if (levelValue < this.level) return;
28
39
 
29
40
  const timestamp = new Date().toLocaleTimeString();
30
- const prefix = `[${timestamp}] [${levelName}]`;
41
+ const color = COLORS[levelName] || '';
42
+ const prefix = `${color}[${timestamp}] [${levelName}]${RESET}`;
31
43
 
32
44
  switch (levelValue) {
33
45
  case LEVELS.TRACE:
@@ -36,11 +48,11 @@ class ConsoleLogger {
36
48
  console.log(prefix, ...args);
37
49
  break;
38
50
  case LEVELS.WARN:
39
- console.warn(prefix, ...args); // 输出到 stderr,但通常显示为黄色警告
51
+ console.warn(prefix, ...args);
40
52
  break;
41
53
  case LEVELS.ERROR:
42
54
  case LEVELS.FATAL:
43
- console.error(prefix, ...args); // 输出到 stderr,通常显示为红色错误
55
+ console.error(prefix, ...args);
44
56
  break;
45
57
  }
46
58
  }
@@ -1,8 +1,9 @@
1
1
  import request from '../nodeutils/requestHttp.js'
2
- import { readFileContent, readJsonFile, readXmlFile } from './fileUtils.js'
2
+ import { readFileContent, readJsonFile, readXmlFile, resolveFilePath, listFiles } from './fileUtils.js'
3
3
  import logger from './logger.js';
4
4
  import { getConfig } from './config.js';
5
5
  import os from 'os'
6
+ import path from 'path'
6
7
 
7
8
  /**
8
9
  * 导入Maximo对象配置
@@ -177,7 +178,7 @@ export async function importMaxDomain({fileName, logname}) {
177
178
 
178
179
  logger.warn(`[${logname}]收到响应`);
179
180
  logger.warn(`[${logname}]响应类型:`, typeof response);
180
- logger.debug(`[${logname}]响应内容:`, response);
181
+ logger.warn(`[${logname}]响应内容:`, response);
181
182
 
182
183
  // 检查响应 - requestHttp的响应拦截器已返回res.data
183
184
  if (response !== null && response !== undefined) {
@@ -255,10 +256,23 @@ export async function saveScriptHistory({autoscript, source, version = '', alias
255
256
  */
256
257
  export async function importMaxScript({fileName, logname}) {
257
258
  try {
258
- const jsonContent = readJsonFile(fileName);
259
+ // 使用通用路径转换方法解析路径
260
+ const resolvedFilePath = resolveFilePath(fileName);
261
+ const fileExt = path.extname(resolvedFilePath).toLowerCase();
262
+
263
+ // 如果不是JSON文件(如直接传入了.js/.py脚本文件路径),自动找同目录下的同名JSON配置文件
264
+ let jsonFilePath = resolvedFilePath;
265
+ if (fileExt !== '.json') {
266
+ const dir = path.dirname(resolvedFilePath);
267
+ const baseName = path.basename(resolvedFilePath, fileExt);
268
+ jsonFilePath = path.join(dir, `${baseName}.json`);
269
+ logger.info(`[导入脚本] 传入的是脚本文件,尝试查找配置文件: ${jsonFilePath}`);
270
+ }
271
+
272
+ const jsonContent = readJsonFile(jsonFilePath);
259
273
 
260
274
  if (!jsonContent) {
261
- logger.error(`文件读取失败: ${fileName}`);
275
+ logger.error(`配置文件读取失败: ${jsonFilePath}`);
262
276
  return false;
263
277
  }
264
278
 
@@ -275,12 +289,13 @@ export async function importMaxScript({fileName, logname}) {
275
289
  const scriptExt = isPython ? '.py' : '.js';
276
290
 
277
291
  const actualLogname = logname || autoscript;
278
- logger.info(`[${actualLogname}]成功读取配置文件: ${fileName}`);
292
+ logger.info(`[${actualLogname}]成功读取配置文件: ${jsonFilePath}`);
279
293
  logger.warn(`[${actualLogname}]脚本名称: ${autoscript}`);
280
294
  logger.warn(`[${actualLogname}]脚本语言: ${scriptLanguage}`);
281
295
 
282
- const dirname = fileName.substring(0, fileName.lastIndexOf('/')) || fileName.substring(0, fileName.lastIndexOf('\\')) || '';
283
- const scriptFileName = dirname ? `${dirname}/${autoscript}${scriptExt}` : `${autoscript}${scriptExt}`;
296
+ // 脚本文件路径:在配置文件同目录下,按 autoscript 名称查找
297
+ const dirname = path.dirname(jsonFilePath);
298
+ const scriptFileName = path.join(dirname, `${autoscript}${scriptExt}`);
284
299
 
285
300
  const fileContent = readFileContent(scriptFileName);
286
301
 
@@ -607,4 +622,189 @@ export async function importMaxAppInfo({fileName, logname}) {
607
622
  }
608
623
  return false
609
624
  }
610
- }
625
+ }
626
+ export async function callMaxScript({ apiScriptName, fileName, params = {}, logname }) {
627
+ if (!apiScriptName) {
628
+ logger.error('[callMaxScript]脚本名称不能为空')
629
+ return false
630
+ }
631
+
632
+ const actualLogname = logname || apiScriptName
633
+
634
+ try {
635
+ logger.info(`[${actualLogname}]开始调用脚本: ${apiScriptName}`)
636
+
637
+ let data = null
638
+ if (fileName) {
639
+ const fileContent = readFileContent(fileName)
640
+ if (!fileContent) {
641
+ logger.error(`[${actualLogname}]文件读取失败: ${fileName}`)
642
+ return false
643
+ }
644
+ logger.info(`[${actualLogname}]已读取请求体文件: ${fileName}`)
645
+
646
+ if (fileName.endsWith('.json')) {
647
+ try {
648
+ data = JSON.parse(fileContent)
649
+ } catch (e) {
650
+ logger.error(`[${actualLogname}]JSON解析失败: ${e.message}`)
651
+ return false
652
+ }
653
+ } else {
654
+ data = fileContent
655
+ }
656
+ }
657
+
658
+ const requestConfig = {
659
+ url: `/api/script/${apiScriptName}`,
660
+ method: data ? 'POST' : 'GET',
661
+ params: {
662
+ ...params,
663
+ _langcode: getConfig().langcode,
664
+ }
665
+ }
666
+
667
+ if (data !== null) {
668
+ requestConfig.data = data
669
+ }
670
+
671
+ const response = await request(requestConfig)
672
+
673
+ logger.info(`[${actualLogname}]收到响应`)
674
+ logger.warn(`[${actualLogname}]响应内容:`, response)
675
+
676
+ if (response !== null && response !== undefined) {
677
+ logger.info(`[${actualLogname}]调用成功`)
678
+ return response
679
+ } else {
680
+ logger.error(`[${actualLogname}]调用失败,响应为空或null`)
681
+ return null
682
+ }
683
+ } catch (error) {
684
+ logger.error(`[${actualLogname}]请求出错:`, error.message)
685
+ if (error.response) {
686
+ logger.error(`[${actualLogname}]服务器返回错误:`, error.response.data)
687
+ } else if (error.request) {
688
+ logger.error(`[${actualLogname}]请求已发出但无响应`)
689
+ } else {
690
+ logger.error(`[${actualLogname}]错误详情:`, error)
691
+ }
692
+ return null
693
+ }
694
+ }
695
+
696
+ /**
697
+ * 通用批量导入 - 支持各种导入功能(对象、脚本、应用信息、域、应用XML等)的批量处理
698
+ * @param {Object} options - 配置选项
699
+ * @param {string} [options.dirName] - 目录名(与files二选一),自动列出目录下符合条件的文件
700
+ * @param {string[]} [options.files] - 文件路径数组(与dirName二选一),直接指定要导入的文件
701
+ * @param {string} [options.extFilter] - 扩展名过滤,如 '.json'、'.xml'(配合dirName使用)
702
+ * @param {boolean} [options.recursive=true] - 是否递归检索子目录(配合dirName使用)
703
+ * @param {Function} options.importFn - 导入函数,接收 {fileName, logname} 参数,如 importMaxObject、importMaxScript 等
704
+ * @param {string} [options.logname] - 日志打印名称
705
+ * @param {number} [options.concurrency=1] - 并发数量,默认1串行执行
706
+ * @param {boolean} [options.continueOnError=true] - 单个文件失败时是否继续,默认继续
707
+ * @returns {Promise<Object>} 批量导入统计结果
708
+ */
709
+ export async function batchImport({ dirName, files, extFilter, recursive = true, importFn, logname = '批量导入', concurrency = 1, continueOnError = true }) {
710
+ if (typeof importFn !== 'function') {
711
+ logger.error('[batchImport]必须指定importFn导入函数')
712
+ return null;
713
+ }
714
+
715
+ let fileNames = [];
716
+ if (files && Array.isArray(files) && files.length > 0) {
717
+ fileNames = files;
718
+ } else if (dirName) {
719
+ fileNames = listFiles(dirName, extFilter, recursive);
720
+ } else {
721
+ logger.error('[batchImport]必须指定dirName或files');
722
+ return null;
723
+ }
724
+
725
+ if (fileNames.length === 0) {
726
+ logger.warn('[batchImport]没有找到需要导入的文件');
727
+ return null;
728
+ }
729
+
730
+ const stats = {
731
+ fileCount: fileNames.length,
732
+ successCount: 0,
733
+ failCount: 0,
734
+ successList: [],
735
+ failList: [],
736
+ startTime: new Date().getTime(),
737
+ endTime: null,
738
+ totalTime: null
739
+ };
740
+
741
+ logger.warn(`[${logname}]开始批量导入,共 ${fileNames.length} 个文件,并发数: ${concurrency}`);
742
+
743
+ // 并发池处理
744
+ let index = 0;
745
+ const worker = async () => {
746
+ while (index < fileNames.length) {
747
+ const cur = index++;
748
+ const filename = fileNames[cur];
749
+ try {
750
+ const result = await importFn({ fileName: filename, logname });
751
+ if (result) {
752
+ stats.successCount++;
753
+ stats.successList.push(filename);
754
+ } else {
755
+ stats.failCount++;
756
+ stats.failList.push(filename);
757
+ }
758
+ } catch (error) {
759
+ stats.failCount++;
760
+ stats.failList.push(filename);
761
+ logger.error(`[${logname}]导入异常: ${filename}`, error.message);
762
+ if (!continueOnError) {
763
+ throw error;
764
+ }
765
+ }
766
+ logger.warn(`[${logname}]进度: ${cur + 1}/${fileNames.length} 成功: ${stats.successCount} 失败: ${stats.failCount}`);
767
+ }
768
+ };
769
+
770
+ const workerCount = Math.max(1, Math.min(concurrency, fileNames.length));
771
+ await Promise.all(Array.from({ length: workerCount }, () => worker()));
772
+
773
+ stats.endTime = new Date().getTime();
774
+ stats.totalTime = stats.endTime - stats.startTime;
775
+
776
+ logger.warn(`[${logname}]批量导入完成,耗时: ${stats.totalTime}ms,成功: ${stats.successCount},失败: ${stats.failCount}`);
777
+ if (stats.failList.length > 0) {
778
+ logger.warn(`[${logname}]失败文件列表:`);
779
+ stats.failList.forEach(f => logger.warn(` ${f}`));
780
+ }
781
+
782
+ return stats;
783
+ }
784
+
785
+ /**
786
+ * 打印批量导入统计结果
787
+ * @param {Object} stats - batchImport 返回的统计结果
788
+ * @param {string} [logname] - 日志打印名称
789
+ */
790
+ export function printBatchStats(stats, logname = '批量导入') {
791
+ if (!stats) {
792
+ logger.warn(`[${logname}]无统计结果可打印`);
793
+ return;
794
+ }
795
+ const formatDateTime = (timestamp) => {
796
+ const d = new Date(timestamp);
797
+ const pad = n => String(n).padStart(2, '0');
798
+ return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`;
799
+ };
800
+ logger.warn(`[${logname}]========== 批量导入统计 ==========`);
801
+ logger.warn(`[${logname}]总文件数: ${stats.fileCount}`);
802
+ logger.warn(`[${logname}]成功数量: ${stats.successCount}`);
803
+ logger.warn(`[${logname}]失败数量: ${stats.failCount}`);
804
+ logger.warn(`[${logname}]开始时间: ${formatDateTime(stats.startTime)} 结束时间: ${formatDateTime(stats.endTime)} 耗时: ${stats.totalTime}ms`);
805
+ logger.warn(`[${logname}]成功文件列表:`);
806
+ stats.successList.forEach(f => logger.warn(` ${f}`));
807
+ logger.warn(`[${logname}]失败文件列表:`);
808
+ stats.failList.forEach(f => logger.warn(` ${f}`));
809
+ logger.warn(`[${logname}]====================================`);
810
+ }
@@ -27,6 +27,10 @@ function limitLogOutput(data, maxLength = 200) {
27
27
  }
28
28
  }
29
29
 
30
+ function joinUrl(base, path) {
31
+ return `${base.replace(/\/$/, '')}/${path.replace(/^\//, '')}`;
32
+ }
33
+
30
34
  function getLogDir() {
31
35
  return path.join(os.homedir(), '.sks', 'nodeutils', 'logs');
32
36
  }
@@ -39,7 +43,7 @@ function saveRequestLog(config, globalConfig) {
39
43
  }
40
44
 
41
45
  const method = config.method?.toUpperCase() || 'GET';
42
- let url = `${config.url}`;
46
+ let url = joinUrl(globalConfig.baseUrl, config.url);
43
47
 
44
48
  if (config.params && Object.keys(config.params).length > 0) {
45
49
  const paramsStr = Object.entries(config.params)
@@ -79,6 +83,44 @@ function saveRequestLog(config, globalConfig) {
79
83
  }
80
84
  }
81
85
 
86
+ function saveErrorLog(error) {
87
+ try {
88
+ const logDir = getLogDir();
89
+ if (!fs.existsSync(logDir)) {
90
+ fs.mkdirSync(logDir, { recursive: true });
91
+ }
92
+
93
+ const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
94
+ const method = error.config?.method?.toUpperCase() || 'GET';
95
+ const fullUrl = error.config ? joinUrl(getConfig().baseUrl, error.config.url) : 'unknown';
96
+
97
+ const errorLog = {
98
+ timestamp: timestamp,
99
+ method: method,
100
+ url: fullUrl,
101
+ message: error.message,
102
+ status: error.response?.status,
103
+ statusText: error.response?.statusText,
104
+ headers: error.response?.headers,
105
+ data: error.response?.data || error.data,
106
+ config: {
107
+ url: error.config?.url,
108
+ method: error.config?.method,
109
+ params: error.config?.params,
110
+ data: error.config?.data
111
+ }
112
+ };
113
+
114
+ const fileName = `response-error-${method}-${timestamp}.json`;
115
+ const filePath = path.join(logDir, fileName);
116
+
117
+ fs.writeFileSync(filePath, JSON.stringify(errorLog, null, 2), 'utf-8');
118
+
119
+ logger.warn(`[HTTP Log] 错误日志已保存: ${filePath}`);
120
+ } catch (e) {
121
+ logger.error('[HTTP Log] 保存错误日志失败:', e.message);
122
+ }
123
+ }
82
124
  function saveResponseLog(response) {
83
125
  try {
84
126
  const logDir = getLogDir();
@@ -88,12 +130,24 @@ function saveResponseLog(response) {
88
130
 
89
131
  const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
90
132
  const method = response.config.method?.toUpperCase() || 'GET';
133
+ const fullUrl = joinUrl(getConfig().baseUrl, response.config.url);
134
+
135
+ const responseLog = {
136
+ timestamp: timestamp,
137
+ method: method,
138
+ url: fullUrl,
139
+ status: response.status,
140
+ statusText: response.statusText,
141
+ headers: response.headers,
142
+ data: response.data
143
+ };
144
+
91
145
  const fileName = `response-${method}-${timestamp}.json`;
92
146
  const filePath = path.join(logDir, fileName);
93
147
 
94
- fs.writeFileSync(filePath, JSON.stringify(response.data, null, 2), 'utf-8');
148
+ fs.writeFileSync(filePath, JSON.stringify(responseLog, null, 2), 'utf-8');
95
149
 
96
- logger.error(`[HTTP Log] 响应日志已保存: ${filePath}`);
150
+ logger.warn(`[HTTP Log] 响应日志已保存: ${filePath}`);
97
151
  } catch (error) {
98
152
  logger.error('[HTTP Log] 保存响应日志失败:', error.message);
99
153
  }
@@ -140,7 +194,7 @@ function createService() {
140
194
  if (!config.params['_langcode']) {
141
195
  config.params['_langcode'] = globalConfig.langcode || 'zh';
142
196
  }
143
- var url = `${config.method} ${globalConfig.baseUrl}${config.url}`;
197
+ var url = `${config.method} ${joinUrl(globalConfig.baseUrl, config.url)}`;
144
198
  if (config.params && Object.keys(config.params).length > 0) {
145
199
  if (url.indexOf('?') > -1) {
146
200
  url += `&`;
@@ -200,18 +254,27 @@ function createService() {
200
254
  return Promise.reject(new Error('未知错误'));
201
255
  }
202
256
  }, error => {
203
- logger.error('[Response Error] 请求错误:', error.message);
204
257
  try{
205
258
  if(error){
259
+ logger.error('[Response Error] 请求错误:', error.message);
260
+ saveErrorLog(error);
206
261
  logger.debug('[Response Error] 错误对象:', error);
207
262
  if (error.data) {
208
263
  logger.info('[Response Error] 错误error.data:', error.data);
209
264
  }
210
265
  if (error.response&&error.response.data) {
211
266
  logger.info('[Response Error] 错误error.response.data:', error.response.data);
212
- if(error.response.data['oslc:Error']&&error.response.data['oslc:Error']['oslc:extendedError']&&error.response.data['oslc:Error']['oslc:extendedError']['oslc:moreInfo']){
213
- logger.info('[Response Error] 错误error.response.data:', error.response.data['oslc:Error']['oslc:extendedError']['oslc:moreInfo']);
267
+ if (error.response.data['oslc:Error']) {
268
+ if (error.response.data['oslc:Error']['oslc:message']) {
269
+ logger.error('[Response Error] 错误error.response.data.oslc:Error.oslc:message:\x1b[32m['+error.response.data['oslc:Error']['oslc:message']+"]\x1b[0m");
270
+ }else if (error.response.data['oslc:Error']['oslc:extendedError']) {
271
+ if (error.response.data['oslc:Error']['oslc:extendedError']['oslc:moreInfo']) {
272
+ logger.error('[Response Error] 错误error.response.data:', error.response.data['oslc:Error']['oslc:extendedError']['oslc:moreInfo']);
214
273
 
274
+ }
275
+ } else {
276
+ logger.error('[Response Error] 错误error.response.data.oslc:Error', error.response.data['oslc:Error']);
277
+ }
215
278
  }
216
279
  }
217
280
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sks-maximo-utils",
3
- "version": "1.0.5",
3
+ "version": "1.0.7",
4
4
  "description": "Maximo helper tools for updating table structures and data",
5
5
  "type": "module",
6
6
  "main": "index.js",
@@ -10,7 +10,10 @@
10
10
  "scripts": {
11
11
  "test": "echo \"Error: no test specified\" && exit 1"
12
12
  },
13
- "keywords": ["maximo", "sks"],
13
+ "keywords": [
14
+ "maximo",
15
+ "sks"
16
+ ],
14
17
  "author": "shoukaiseki",
15
18
  "license": "ISC",
16
19
  "dependencies": {