openyida 2026.6.1-beta.1 → 2026.6.3-beta.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.
@@ -139,16 +139,28 @@ async function updateFormConfig(appType, formUuid, authRef) {
139
139
  return result;
140
140
  }
141
141
 
142
- // ── 适配 SerialNumberField formula ───────────────────
142
+ // ── 适配 Schema 中的应用 / 表单标识符 ─────────────────
143
143
  //
144
- // Schema 中所有 SerialNumberField formula 里的旧 appType 替换为新 appType,
145
- // 同时将旧 formUuid 替换为新 formUuid。
144
+ // 导入时需要把 Schema 里所有对旧 appType / formUuid 的引用(页面 id、组件
145
+ // props.appType、actions.source、SerialNumberField 公式等)重映射为新值。
146
+ //
147
+ // 早期实现对整个 Schema JSON 做无约束的全局字符串替换,存在数据腐蚀风险:
148
+ // 若某个字段的文本值、label、备注恰好包含旧 appType / formUuid 子串,会被
149
+ // 误替换。这里改为「标识符边界」约束替换——appType / formUuid 都是由字母、
150
+ // 数字、下划线、连字符构成的标识符,只有当匹配片段两侧不是同类标识符字符时
151
+ // 才替换,从而避免误伤包含旧标识符子串的普通文本。
152
+
153
+ function buildIdentifierBoundaryRegExp(identifier) {
154
+ // 标识符字符集:字母、数字、下划线、连字符。
155
+ // 使用 lookbehind / lookahead 保证匹配片段不是更长标识符的一部分。
156
+ return new RegExp(`(?<![A-Za-z0-9_-])${escapeRegExp(identifier)}(?![A-Za-z0-9_-])`, 'g');
157
+ }
146
158
 
147
- function adaptSerialNumberFormulas(schema, oldAppType, newAppType, oldFormUuid, newFormUuid) {
159
+ function adaptSchemaIdentifiers(schema, oldAppType, newAppType, oldFormUuid, newFormUuid) {
148
160
  const schemaStr = JSON.stringify(schema);
149
161
  const adapted = schemaStr
150
- .replace(new RegExp(escapeRegExp(oldAppType), 'g'), newAppType)
151
- .replace(new RegExp(escapeRegExp(oldFormUuid), 'g'), newFormUuid);
162
+ .replace(buildIdentifierBoundaryRegExp(oldAppType), newAppType)
163
+ .replace(buildIdentifierBoundaryRegExp(oldFormUuid), newFormUuid);
152
164
  return JSON.parse(adapted);
153
165
  }
154
166
 
@@ -287,8 +299,8 @@ async function run(args) {
287
299
  continue;
288
300
  }
289
301
 
290
- // 将 Schema 中所有旧 appType / formUuid 替换为新值
291
- const adaptedSchema = adaptSerialNumberFormulas(
302
+ // 将 Schema 中所有旧 appType / formUuid 引用重映射为新值(标识符边界约束,避免误伤普通文本)
303
+ const adaptedSchema = adaptSchemaIdentifiers(
292
304
  originalSchema,
293
305
  sourceAppType,
294
306
  newAppType,
@@ -377,7 +389,7 @@ module.exports = {
377
389
  __test__: {
378
390
  normalizeImportFormType,
379
391
  buildCreateFormPostData,
380
- adaptSerialNumberFormulas,
392
+ adaptSchemaIdentifiers,
381
393
  extractSchemaContent,
382
394
  },
383
395
  };
@@ -2,6 +2,42 @@
2
2
  * 执行动作配置生成模块
3
3
  */
4
4
 
5
+ // 可能携带凭证 / 敏感信息的请求头(小写匹配)。
6
+ // 这些头的真实值绝不能写入生成的 Action 配置,否则会把用户 token 泄漏到
7
+ // 落盘的连接器配置里。命中时用占位符替换,提示用户在连接器认证中配置。
8
+ const SENSITIVE_HEADER_PATTERNS = [
9
+ 'authorization',
10
+ 'cookie',
11
+ 'token',
12
+ 'api-key',
13
+ 'apikey',
14
+ 'secret',
15
+ 'access-token',
16
+ 'x-acs-dingtalk-access-token',
17
+ 'x-api-key',
18
+ 'proxy-authorization',
19
+ ];
20
+
21
+ function isSensitiveHeader(headerName) {
22
+ const lower = String(headerName || '').toLowerCase();
23
+ return SENSITIVE_HEADER_PATTERNS.some((pattern) => lower.includes(pattern));
24
+ }
25
+
26
+ /**
27
+ * 计算请求头写入 Action 配置的默认值。
28
+ * 敏感头返回占位符(不泄漏真实凭证),普通头截断展示前 50 字符。
29
+ * @param {string} headerName - 请求头名称
30
+ * @param {string} headerValue - 请求头原始值
31
+ * @returns {string} 可安全写入配置的默认值
32
+ */
33
+ function buildHeaderDefaultValue(headerName, headerValue) {
34
+ if (isSensitiveHeader(headerName)) {
35
+ return '';
36
+ }
37
+ const value = String(headerValue === null || headerValue === undefined ? '' : headerValue);
38
+ return value.length > 50 ? value.substring(0, 50) + '...' : value;
39
+ }
40
+
5
41
  /**
6
42
  * 根据URL路径生成有意义的动作名称和描述
7
43
  * @param {string} path - URL路径
@@ -125,7 +161,7 @@ function generateOperation(curlData, relevantHeaders) {
125
161
  paramType: 'String',
126
162
  desc: key,
127
163
  required: false,
128
- defaultValue: value.substring(0, 50) + (value.length > 50 ? '...' : ''),
164
+ defaultValue: buildHeaderDefaultValue(key, value),
129
165
  children: [],
130
166
  childList: [],
131
167
  __level: 0,
@@ -262,5 +298,7 @@ function generateOperation(curlData, relevantHeaders) {
262
298
 
263
299
  module.exports = {
264
300
  generateActionInfo,
265
- generateOperation
301
+ generateOperation,
302
+ isSensitiveHeader,
303
+ buildHeaderDefaultValue
266
304
  };
@@ -60,7 +60,13 @@ function printTable(headers, rows) {
60
60
  function buildOperationsSummary(operations) {
61
61
  if (!Array.isArray(operations) || operations.length === 0) {return '';}
62
62
 
63
- const summaries = operations.map(op => op.summary || op.operationId);
63
+ const summaries = operations
64
+ .map(op => op.summary || op.name || op.operationId || op.id || op.url || op.path)
65
+ .filter(Boolean);
66
+
67
+ if (summaries.length === 0) {
68
+ return '';
69
+ }
64
70
 
65
71
  if (summaries.length === 1) {
66
72
  return `支持${summaries[0]}`;
@@ -23,6 +23,7 @@ const {
23
23
  getConnectorDetail,
24
24
  saveConnector,
25
25
  } = require('./api');
26
+ const { normalizeOperations } = require('./operation-normalizer');
26
27
 
27
28
  function showUsage() {
28
29
  console.log(`
@@ -99,6 +100,7 @@ async function run(args) {
99
100
  if (!Array.isArray(newOperations)) {
100
101
  newOperations = [newOperations];
101
102
  }
103
+ newOperations = normalizeOperations(newOperations);
102
104
  console.log(`✓ 已加载 ${newOperations.length} 个执行动作`);
103
105
  newOperations.forEach((operation, index) => {
104
106
  console.log(` [${index + 1}] ${operation.operationId}: ${operation.summary}`);
@@ -123,7 +125,7 @@ async function run(args) {
123
125
  const detail = await getConnectorDetail(connector.connectorName, authRef);
124
126
  targetConnector = { ...connector, detail };
125
127
 
126
- const existingOps = JSON.parse(targetConnector.detail.operations || '[]');
128
+ const existingOps = normalizeOperations(JSON.parse(targetConnector.detail.operations || '[]'));
127
129
  console.log('📋 即将追加到以下连接器:');
128
130
  console.log(` 名称: ${targetConnector.displayName}`);
129
131
  console.log(` ID: ${targetConnector.id}`);
@@ -164,7 +166,7 @@ async function run(args) {
164
166
  }
165
167
 
166
168
  // 合并执行动作
167
- const existingOperations = JSON.parse(targetConnector.detail.operations || '[]');
169
+ const existingOperations = normalizeOperations(JSON.parse(targetConnector.detail.operations || '[]'));
168
170
  const existingIds = new Set(existingOperations.map(op => op.operationId));
169
171
  const duplicates = newOperations.filter(op => existingIds.has(op.operationId));
170
172
 
@@ -31,6 +31,7 @@ const {
31
31
  getConnectorDetail,
32
32
  saveConnector,
33
33
  } = require('./api');
34
+ const { normalizeOperations } = require('./operation-normalizer');
34
35
 
35
36
  function showUsage() {
36
37
  console.log(`
@@ -232,11 +233,11 @@ async function run(args) {
232
233
  console.log(`当前描述: ${connector.connectorDesc || '(空)'}\n`);
233
234
 
234
235
  const detail = await getConnectorDetail(connector.connectorName, authRef);
235
- const currentOperations = JSON.parse(detail.operations || '[]');
236
+ const currentOperations = normalizeOperations(JSON.parse(detail.operations || '[]'));
236
237
  const newDesc = buildConnectorDesc(options.description, connector.connectorDesc, authRef, currentOperations);
237
238
 
238
239
  await saveConnector({
239
- operations: detail.operations || '[]',
240
+ operations: JSON.stringify(currentOperations),
240
241
  displayName: detail.displayName,
241
242
  iconUrl: detail.iconUrl || 'chaxun%%#FFA200',
242
243
  connectorDesc: newDesc,
@@ -278,6 +279,7 @@ async function run(args) {
278
279
  fail('错误: operations 文件不能为空数组');
279
280
  process.exit(1);
280
281
  }
282
+ operations = normalizeOperations(operations);
281
283
  console.log(`✓ 已加载 ${operations.length} 个执行动作`);
282
284
  } catch (e) {
283
285
  fail(`读取执行动作配置文件失败: ${e.message}`);
@@ -17,6 +17,7 @@ const {
17
17
  getConnectorDetail,
18
18
  saveConnector,
19
19
  } = require('./api');
20
+ const { normalizeOperations } = require('./operation-normalizer');
20
21
 
21
22
  function showUsage() {
22
23
  console.log(`
@@ -63,7 +64,7 @@ async function run(args) {
63
64
  }
64
65
 
65
66
  const detail = await getConnectorDetail(connector.connectorName, authRef);
66
- const currentOperations = JSON.parse(detail.operations || '[]');
67
+ const currentOperations = normalizeOperations(JSON.parse(detail.operations || '[]'));
67
68
  const targetOperation = currentOperations.find(op => op.operationId === operationId);
68
69
 
69
70
  if (!targetOperation) {
@@ -2,6 +2,36 @@
2
2
  * Curl 命令解析模块
3
3
  */
4
4
 
5
+ /**
6
+ * 从 curl 命令中提取 URL。
7
+ * 早期实现只匹配 `curl "<url>"`(必须带引号且紧跟 curl),导致裸 URL、
8
+ * 单引号、`--url` 参数、`-X POST` 在 URL 之前等常见写法全部解析失败。
9
+ * 这里按优先级多策略提取:
10
+ * 1. 显式 `--url <url>` 参数
11
+ * 2. 命令中任意带引号的 http(s) URL
12
+ * 3. 命令中任意裸 http(s) URL(截断到空白处)
13
+ * @param {string} curlCommand - curl 命令字符串
14
+ * @returns {string} 提取到的 URL,未找到返回空字符串
15
+ */
16
+ function extractCurlUrl(curlCommand) {
17
+ const explicitUrl = curlCommand.match(/--url\s+['"]?([^'"\s]+)['"]?/);
18
+ if (explicitUrl) {
19
+ return explicitUrl[1];
20
+ }
21
+
22
+ const quotedUrl = curlCommand.match(/['"](https?:\/\/[^'"]+)['"]/);
23
+ if (quotedUrl) {
24
+ return quotedUrl[1];
25
+ }
26
+
27
+ const bareUrl = curlCommand.match(/(https?:\/\/[^\s'"]+)/);
28
+ if (bareUrl) {
29
+ return bareUrl[1];
30
+ }
31
+
32
+ return '';
33
+ }
34
+
5
35
  /**
6
36
  * 解析 curl 命令
7
37
  * @param {string} curlCommand - curl 命令字符串
@@ -19,10 +49,9 @@ function parseCurl(curlCommand) {
19
49
  };
20
50
 
21
51
  try {
22
- // 提取 URL
23
- const urlMatch = curlCommand.match(/curl\s+['"]([^'"]+)['"]/);
24
- if (urlMatch) {
25
- result.url = urlMatch[1];
52
+ // 提取 URL:兼容带引号 URL、裸 URL、--url 参数、-X POST 在前等多种写法
53
+ result.url = extractCurlUrl(curlCommand);
54
+ if (result.url) {
26
55
  const url = new URL(result.url);
27
56
  result.protocol = url.protocol.replace(':', '');
28
57
  result.host = url.hostname;
@@ -30,10 +59,10 @@ function parseCurl(curlCommand) {
30
59
  }
31
60
 
32
61
  // 提取方法
33
- const methodMatch = curlCommand.match(/-X\s+(\w+)/);
62
+ const methodMatch = curlCommand.match(/(?:-X|--request)\s+['"]?(\w+)['"]?/);
34
63
  if (methodMatch) {
35
64
  result.method = methodMatch[1].toUpperCase();
36
- } else if (curlCommand.includes('--data') || curlCommand.includes('-d')) {
65
+ } else if (curlCommand.includes('--data') || /\s-d\b/.test(curlCommand)) {
37
66
  result.method = 'POST';
38
67
  }
39
68
 
@@ -64,10 +93,11 @@ function detectAuthType(headers) {
64
93
  const authHeader = headers['Authorization'] || headers['authorization'];
65
94
 
66
95
  if (authHeader) {
67
- if (authHeader.startsWith('Bearer')) {
96
+ const scheme = authHeader.trim().toLowerCase();
97
+ if (scheme.startsWith('bearer')) {
68
98
  return { type: 'API密钥', code: 'ApiKeyAuth', headerName: 'Authorization' };
69
99
  }
70
- if (authHeader.startsWith('Basic')) {
100
+ if (scheme.startsWith('basic')) {
71
101
  return { type: '基本身份验证', code: 'BasicAuth', headerName: 'Authorization' };
72
102
  }
73
103
  }
@@ -116,6 +146,7 @@ function filterBrowserHeaders(headers) {
116
146
 
117
147
  module.exports = {
118
148
  parseCurl,
149
+ extractCurlUrl,
119
150
  detectAuthType,
120
151
  filterBrowserHeaders,
121
152
  BROWSER_HEADERS
@@ -104,21 +104,22 @@ class MarkdownParser extends BaseDocParser {
104
104
  }
105
105
 
106
106
  parseRequestHeaders() {
107
- const headerSection = this.extractSection('请求头', '请求参数');
107
+ const headerSection = this.extractSection('请求头', ['查询参数', '请求参数', '请求体', '响应']);
108
108
  if (headerSection) {
109
109
  this.result.requestInfo.headers = this.parseTable(headerSection);
110
110
  }
111
111
  }
112
112
 
113
113
  parseQueryParams() {
114
- const querySection = this.extractSection('查询参数', '请求体');
114
+ const querySection = this.extractSection('查询参数', ['请求体', '响应', '错误码']) ||
115
+ this.extractSection('请求参数', ['请求体', '响应', '错误码']);
115
116
  if (querySection) {
116
117
  this.result.requestInfo.query = this.parseTable(querySection);
117
118
  }
118
119
  }
119
120
 
120
121
  parseRequestBody() {
121
- const bodySection = this.extractSection('请求体', '响应');
122
+ const bodySection = this.extractSection('请求体', ['响应', '返回', '错误码']);
122
123
  if (!bodySection) {return;}
123
124
 
124
125
  const jsonMatch = bodySection.match(/```json\s*([\s\S]*?)```/);
@@ -289,10 +290,13 @@ class MarkdownParser extends BaseDocParser {
289
290
 
290
291
  let endIndex = this.content.length;
291
292
  if (endMarker) {
292
- const endMatch = this.content.indexOf(endMarker, startIndex + startMarker.length);
293
- if (endMatch !== -1) {
294
- endIndex = endMatch;
295
- }
293
+ const markers = Array.isArray(endMarker) ? endMarker : [endMarker];
294
+ markers.forEach(marker => {
295
+ const endMatch = this.content.indexOf(marker, startIndex + startMarker.length);
296
+ if (endMatch !== -1 && endMatch < endIndex) {
297
+ endIndex = endMatch;
298
+ }
299
+ });
296
300
  }
297
301
 
298
302
  return this.content.substring(startIndex, endIndex);
@@ -343,6 +347,15 @@ class MarkdownParser extends BaseDocParser {
343
347
  }
344
348
  }
345
349
 
350
+ function applyParamLocation(nodes, paramLocation) {
351
+ (Array.isArray(nodes) ? nodes : []).forEach(node => {
352
+ node.paramLocation = paramLocation;
353
+ applyParamLocation(node.childList, paramLocation);
354
+ applyParamLocation(node.children, paramLocation);
355
+ });
356
+ return nodes;
357
+ }
358
+
346
359
  /**
347
360
  * 文档解析器工厂
348
361
  */
@@ -424,6 +437,7 @@ function convertToOperationConfig(parseResult) {
424
437
  childList: [],
425
438
  __level: 0,
426
439
  hidden: false,
440
+ paramLocation: 'header',
427
441
  required: h.required === 'true' || h.required === '是' || h.required === '**' || h.required === true,
428
442
  defaultValue: h.example || h.value || ''
429
443
  }));
@@ -433,7 +447,8 @@ function convertToOperationConfig(parseResult) {
433
447
  desc: '请求头',
434
448
  name: 'Headers',
435
449
  paramType: 'Object',
436
- required: false
450
+ required: false,
451
+ paramLocation: 'header'
437
452
  });
438
453
 
439
454
  operation.parameters.header = parseResult.requestInfo.headers.map(h => ({
@@ -452,7 +467,8 @@ function convertToOperationConfig(parseResult) {
452
467
  children: [],
453
468
  childList: [],
454
469
  __level: 0,
455
- hidden: false
470
+ hidden: false,
471
+ paramLocation: 'query'
456
472
  }));
457
473
 
458
474
  operation.inputs.push({
@@ -460,7 +476,8 @@ function convertToOperationConfig(parseResult) {
460
476
  desc: '查询参数',
461
477
  name: 'Query',
462
478
  paramType: 'Object',
463
- required: false
479
+ required: false,
480
+ paramLocation: 'query'
464
481
  });
465
482
 
466
483
  operation.parameters.query = parseResult.requestInfo.query.map(q => ({
@@ -478,7 +495,7 @@ function convertToOperationConfig(parseResult) {
478
495
  default: JSON.stringify(requestExample)
479
496
  };
480
497
 
481
- const requestChildList = generateChildList(requestSchema, operation.operationId);
498
+ const requestChildList = applyParamLocation(generateChildList(requestSchema, operation.operationId), 'body');
482
499
  if (requestChildList.length > 0) {
483
500
  operation.inputs.push({
484
501
  defaultValue: JSON.stringify(requestExample),
@@ -486,7 +503,8 @@ function convertToOperationConfig(parseResult) {
486
503
  name: 'Body',
487
504
  paramType: 'Object',
488
505
  required: false,
489
- childList: requestChildList
506
+ childList: requestChildList,
507
+ paramLocation: 'body'
490
508
  });
491
509
  }
492
510
  }
@@ -496,13 +514,6 @@ function convertToOperationConfig(parseResult) {
496
514
  const responseSchema = parseResult.responseInfo.schema;
497
515
  operation.responses = responseSchema;
498
516
 
499
- if (!operation.parameters.body) {
500
- const example = generateExample(responseSchema);
501
- operation.parameters.body = {
502
- default: JSON.stringify(example)
503
- };
504
- }
505
-
506
517
  operation.outputs = [generateOutputs(responseSchema, operation.operationId)];
507
518
  } else {
508
519
  operation.responses = { type: 'object' };
@@ -0,0 +1,198 @@
1
+ 'use strict';
2
+
3
+ const { generateOutputs } = require('./response-parser');
4
+
5
+ function slugifyOperationId(value, fallback) {
6
+ const slug = String(value || '')
7
+ .trim()
8
+ .replace(/^\//, '')
9
+ .replace(/[^A-Za-z0-9_]+/g, '_')
10
+ .replace(/^_+|_+$/g, '');
11
+
12
+ return slug || fallback;
13
+ }
14
+
15
+ function deriveOperationId(operation, index) {
16
+ const explicitId = String(operation.operationId || '').trim();
17
+ if (explicitId) {
18
+ return explicitId;
19
+ }
20
+
21
+ const fallback = `operation_${index + 1}`;
22
+ const candidates = [operation.url, operation.path, operation.id, operation.name, operation.summary];
23
+ for (const candidate of candidates) {
24
+ const operationId = slugifyOperationId(candidate, '');
25
+ if (operationId) {
26
+ return operationId;
27
+ }
28
+ }
29
+
30
+ return fallback;
31
+ }
32
+
33
+ function normalizeUrl(value) {
34
+ return String(value || '').trim().replace(/^\/+/, '');
35
+ }
36
+
37
+ function inferParamLocation(input) {
38
+ if (input.paramLocation) {
39
+ return input.paramLocation;
40
+ }
41
+
42
+ const name = String(input.name || '').toLowerCase();
43
+ if (name === 'headers' || name === 'header') {
44
+ return 'header';
45
+ }
46
+ if (name === 'query' || name === 'queries') {
47
+ return 'query';
48
+ }
49
+ if (name === 'path' || name === 'paths') {
50
+ return 'path';
51
+ }
52
+ if (name === 'body') {
53
+ return 'body';
54
+ }
55
+
56
+ return null;
57
+ }
58
+
59
+ function normalizeParamNode(node, paramLocation) {
60
+ if (!node || typeof node !== 'object') {
61
+ return node;
62
+ }
63
+
64
+ const normalized = { ...node };
65
+ if (paramLocation && !normalized.paramLocation) {
66
+ normalized.paramLocation = paramLocation;
67
+ }
68
+ if (!Array.isArray(normalized.children)) {
69
+ normalized.children = [];
70
+ }
71
+ if (!Array.isArray(normalized.childList)) {
72
+ normalized.childList = [];
73
+ }
74
+ normalized.children = normalized.children.map(child => normalizeParamNode(child, paramLocation));
75
+ normalized.childList = normalized.childList.map(child => normalizeParamNode(child, paramLocation));
76
+ return normalized;
77
+ }
78
+
79
+ function normalizeInput(input) {
80
+ if (!input || typeof input !== 'object') {
81
+ return input;
82
+ }
83
+
84
+ const paramLocation = inferParamLocation(input);
85
+ const normalized = { ...input };
86
+ if (paramLocation && !normalized.paramLocation) {
87
+ normalized.paramLocation = paramLocation;
88
+ }
89
+ if (!Array.isArray(normalized.childList)) {
90
+ normalized.childList = [];
91
+ }
92
+ normalized.childList = normalized.childList.map(child => normalizeParamNode(child, paramLocation));
93
+ return normalized;
94
+ }
95
+
96
+ function buildParametersFromInputs(inputs) {
97
+ const parameters = { header: [] };
98
+
99
+ for (const input of inputs) {
100
+ if (!input || typeof input !== 'object') {
101
+ continue;
102
+ }
103
+
104
+ const location = inferParamLocation(input);
105
+ const childList = Array.isArray(input.childList) ? input.childList : [];
106
+
107
+ if (location === 'header') {
108
+ parameters.header = childList.map(child => ({
109
+ name: child.name,
110
+ value: child.defaultValue || '',
111
+ }));
112
+ } else if (location === 'query') {
113
+ parameters.query = childList.map(child => ({
114
+ name: child.name,
115
+ value: child.defaultValue || '',
116
+ }));
117
+ } else if (location === 'body' && input.defaultValue !== undefined) {
118
+ parameters.body = { default: input.defaultValue };
119
+ }
120
+ }
121
+
122
+ return parameters;
123
+ }
124
+
125
+ function normalizeParameters(parameters, inputs) {
126
+ const normalized = parameters && typeof parameters === 'object' && !Array.isArray(parameters)
127
+ ? { ...parameters }
128
+ : buildParametersFromInputs(inputs);
129
+
130
+ if (!Array.isArray(normalized.header)) {
131
+ normalized.header = [];
132
+ }
133
+
134
+ return normalized;
135
+ }
136
+
137
+ function normalizeOutput(output) {
138
+ if (!output || typeof output !== 'object') {
139
+ return output;
140
+ }
141
+
142
+ const normalized = { ...output };
143
+ if (!Array.isArray(normalized.childList)) {
144
+ normalized.childList = [];
145
+ }
146
+ return normalized;
147
+ }
148
+
149
+ function normalizeOperation(operation, index) {
150
+ if (!operation || typeof operation !== 'object' || Array.isArray(operation)) {
151
+ throw new Error(`第 ${index + 1} 个执行动作必须是对象`);
152
+ }
153
+
154
+ const operationId = deriveOperationId(operation, index);
155
+ const rawUrl = operation.url !== undefined && operation.url !== null ? operation.url : operation.path;
156
+ const hasUrl = rawUrl !== undefined && rawUrl !== null && String(rawUrl).trim() !== '';
157
+ const url = normalizeUrl(rawUrl);
158
+
159
+ if (!hasUrl) {
160
+ throw new Error(`第 ${index + 1} 个执行动作缺少 url/path`);
161
+ }
162
+
163
+ const summary = operation.summary || operation.name || operationId;
164
+ const description = operation.description || operation.desc || summary;
165
+ const method = String(operation.method || 'get').toLowerCase();
166
+ const inputs = Array.isArray(operation.inputs)
167
+ ? operation.inputs.map(normalizeInput)
168
+ : [];
169
+ const responses = operation.responses || { type: 'object', properties: {} };
170
+ const outputs = Array.isArray(operation.outputs) && operation.outputs.length > 0
171
+ ? operation.outputs.map(normalizeOutput)
172
+ : [generateOutputs(responses, operationId)];
173
+
174
+ return {
175
+ ...operation,
176
+ id: operation.id || `operation-${operationId}`,
177
+ operationId,
178
+ summary,
179
+ description,
180
+ url,
181
+ method,
182
+ inputs,
183
+ parameters: normalizeParameters(operation.parameters, inputs),
184
+ responses,
185
+ outputs,
186
+ origin: operation.origin !== undefined ? operation.origin : true,
187
+ };
188
+ }
189
+
190
+ function normalizeOperations(operations) {
191
+ const list = Array.isArray(operations) ? operations : [operations];
192
+ return list.map(normalizeOperation);
193
+ }
194
+
195
+ module.exports = {
196
+ normalizeOperation,
197
+ normalizeOperations,
198
+ };
package/lib/core/chalk.js CHANGED
@@ -36,6 +36,8 @@ const c = {
36
36
  cyan: '\x1b[36m',
37
37
  white: '\x1b[37m',
38
38
  gray: '\x1b[90m',
39
+ bgBlue: '\x1b[44m',
40
+ bgCyan: '\x1b[46m',
39
41
  };
40
42
 
41
43
  // ── 图标常量 ───────────────────────────────────────────
@@ -13,7 +13,7 @@
13
13
 
14
14
  const { execSync } = require('child_process');
15
15
  const readline = require('readline');
16
- const { warn } = require('./chalk');
16
+ const { c, warn } = require('./chalk');
17
17
  const {
18
18
  loadEnvsConfig,
19
19
  saveEnvsConfig,
@@ -28,14 +28,14 @@ const {
28
28
 
29
29
  // ── 颜色常量 ──────────────────────────────────────────
30
30
 
31
- const RESET = '\x1b[0m';
32
- const BOLD = '\x1b[1m';
33
- const DIM = '\x1b[2m';
34
- const GREEN = '\x1b[32m';
35
- const YELLOW = '\x1b[33m';
36
- const CYAN = '\x1b[36m';
37
- const RED = '\x1b[31m';
38
- const BLUE = '\x1b[34m';
31
+ const RESET = c.reset;
32
+ const BOLD = c.bold;
33
+ const DIM = c.dim;
34
+ const GREEN = c.green;
35
+ const YELLOW = c.yellow;
36
+ const CYAN = c.cyan;
37
+ const RED = c.red;
38
+ const BLUE = c.blue;
39
39
 
40
40
  // ── 工具函数 ──────────────────────────────────────────
41
41
 
@@ -12,18 +12,18 @@
12
12
  const { execSync } = require('child_process');
13
13
  const { fetchLatestVersion, isNewer } = require('./check-update');
14
14
  const { t } = require('./i18n');
15
- const { warn } = require('./chalk');
15
+ const { c, warn } = require('./chalk');
16
16
  const { getNpmExecutable } = require('./utils');
17
17
 
18
18
  // ── ANSI 颜色常量 ──────────────────────────────────
19
- const RESET = '\x1b[0m';
20
- const BOLD = '\x1b[1m';
21
- const DIM = '\x1b[2m';
22
- const GREEN = '\x1b[32m';
23
- const YELLOW = '\x1b[33m';
24
- const CYAN = '\x1b[36m';
25
- const RED = '\x1b[31m';
26
- const MAGENTA = '\x1b[35m';
19
+ const RESET = c.reset;
20
+ const BOLD = c.bold;
21
+ const DIM = c.dim;
22
+ const GREEN = c.green;
23
+ const YELLOW = c.yellow;
24
+ const CYAN = c.cyan;
25
+ const RED = c.red;
26
+ const MAGENTA = c.magenta;
27
27
 
28
28
  // ── Spinner 动画 ───────────────────────────────────
29
29
  const SPINNER_FRAMES = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
@@ -12,7 +12,7 @@
12
12
 
13
13
  const { execSync, spawn } = require('child_process');
14
14
  const { t } = require('../core/i18n');
15
- const { warn } = require('../core/chalk');
15
+ const { c, warn } = require('../core/chalk');
16
16
 
17
17
  const DWS_BINARY_NAME = 'dws';
18
18
  const INSTALL_SCRIPTS = {
@@ -51,16 +51,16 @@ function getDwsVersion() {
51
51
  * 显示安装指引
52
52
  */
53
53
  function showInstallGuide() {
54
- const RESET = '\x1b[0m';
55
- const BOLD = '\x1b[1m';
56
- const DIM = '\x1b[2m';
57
- const CYAN = '\x1b[36m';
58
- const GREEN = '\x1b[32m';
59
- const YELLOW = '\x1b[33m';
60
- const BLUE = '\x1b[34m';
54
+ const RESET = c.reset;
55
+ const BOLD = c.bold;
56
+ const DIM = c.dim;
57
+ const CYAN = c.cyan;
58
+ const GREEN = c.green;
59
+ const YELLOW = c.yellow;
60
+ const BLUE = c.blue;
61
61
 
62
- const BG_BLUE = '\x1b[44m';
63
- const WHITE = '\x1b[37m';
62
+ const BG_BLUE = c.bgBlue;
63
+ const WHITE = c.white;
64
64
 
65
65
  const isWindows = process.platform === 'win32';
66
66
  const installCommand = isWindows ? INSTALL_SCRIPTS.windows : INSTALL_SCRIPTS.unix;
@@ -221,11 +221,11 @@ async function executeDwsCommand(args) {
221
221
  * 显示 dws 帮助信息
222
222
  */
223
223
  function showDwsHelp() {
224
- const RESET = '\x1b[0m';
225
- const BOLD = '\x1b[1m';
226
- const CYAN = '\x1b[36m';
227
- const GREEN = '\x1b[32m';
228
- const YELLOW = '\x1b[33m';
224
+ const RESET = c.reset;
225
+ const BOLD = c.bold;
226
+ const CYAN = c.cyan;
227
+ const GREEN = c.green;
228
+ const YELLOW = c.yellow;
229
229
 
230
230
  console.log('');
231
231
  console.log(`${BOLD}openyida dws - 钉钉 CLI 集成${RESET}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openyida",
3
- "version": "2026.6.1-beta.1",
3
+ "version": "2026.6.3-beta.0",
4
4
  "description": "OpenYida CLI - 宜搭低代码 AI 开发工具(安装即用,零配置)",
5
5
  "bin": {
6
6
  "openyida": "bin/yida.js",