yidaconnector 2026.6.11
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/LICENSE +21 -0
- package/README.md +383 -0
- package/bin/yida.js +670 -0
- package/lib/app/form-navigation.js +58 -0
- package/lib/app/get-schema.js +538 -0
- package/lib/auth/auth.js +294 -0
- package/lib/auth/cdp-browser-login.js +390 -0
- package/lib/auth/codex-login.js +71 -0
- package/lib/auth/login.js +475 -0
- package/lib/auth/org.js +363 -0
- package/lib/auth/qr-login.js +1563 -0
- package/lib/core/chalk.js +384 -0
- package/lib/core/check-update.js +82 -0
- package/lib/core/cli-error.js +39 -0
- package/lib/core/command-manifest.js +106 -0
- package/lib/core/env-cmd.js +545 -0
- package/lib/core/env-manager.js +601 -0
- package/lib/core/env.js +287 -0
- package/lib/core/i18n.js +177 -0
- package/lib/core/locales/ar.js +805 -0
- package/lib/core/locales/de.js +805 -0
- package/lib/core/locales/en.js +1623 -0
- package/lib/core/locales/es.js +805 -0
- package/lib/core/locales/fr.js +805 -0
- package/lib/core/locales/hi.js +805 -0
- package/lib/core/locales/ja.js +1197 -0
- package/lib/core/locales/ko.js +807 -0
- package/lib/core/locales/pt.js +805 -0
- package/lib/core/locales/vi.js +805 -0
- package/lib/core/locales/zh-HK.js +1233 -0
- package/lib/core/locales/zh.js +1584 -0
- package/lib/core/query-data.js +781 -0
- package/lib/core/redact.js +100 -0
- package/lib/core/utils.js +799 -0
- package/lib/core/yida-client.js +117 -0
- package/package.json +94 -0
- package/project/config.json +4 -0
- package/project/pages/src/demo-birthday-game.oyd.jsx +832 -0
- package/project/pages/src/demo-chip-insight.oyd.jsx +983 -0
- package/project/pages/src/demo-compat-smoke.oyd.jsx +58 -0
- package/project/pages/src/demo-crm-batch-entry.oyd.jsx +805 -0
- package/project/pages/src/demo-crm-dashboard.oyd.jsx +677 -0
- package/project/pages/src/demo-future-vision-2026.oyd.jsx +1102 -0
- package/project/pages/src/demo-ppt.oyd.jsx +1192 -0
- package/project/pages/src/demo-salary-calculator.oyd.jsx +904 -0
- package/project/pages/src/yidaconnector-knowledge-doc.oyd.jsx +1714 -0
- package/project/prd/demo-birthday-game.md +39 -0
- package/project/prd/demo-crm.md +463 -0
- package/project/prd/demo-dingtalk-ai-solution-center.md +425 -0
- package/project/prd/demo-future-vision-2026.md +78 -0
- package/project/prd/demo-salary-calculator.md +101 -0
- package/scripts/build-skills-package.js +406 -0
- package/scripts/check-syntax.js +59 -0
- package/scripts/demo-dws.sh +106 -0
- package/scripts/e2e-real/cleanup.js +67 -0
- package/scripts/e2e-real/fixtures/form-fields.json +18 -0
- package/scripts/e2e-real/full-runner.js +1566 -0
- package/scripts/e2e-real/runner.js +293 -0
- package/scripts/e2e-real/skill-coverage.js +115 -0
- package/scripts/generate-command-docs.js +109 -0
- package/scripts/nightly-smoke.js +134 -0
- package/scripts/postinstall.js +545 -0
- package/scripts/solution-center-runner.js +368 -0
- package/scripts/validate-ci.sh +50 -0
- package/scripts/validate-command-manifest.js +119 -0
- package/scripts/validate-package-size.js +78 -0
- package/scripts/validate-skills.js +247 -0
- package/scripts/validate-structure.js +66 -0
- package/yida-skills/SKILL.md +163 -0
- package/yida-skills/references/yida-api.md +1309 -0
- package/yida-skills/skills/large-file-write/SKILL.md +91 -0
- package/yida-skills/skills/large-file-write/references/write-patterns.md +149 -0
- package/yida-skills/skills/large-file-write/scripts/write.js +157 -0
- package/yida-skills/skills/yida-data-management/SKILL.md +252 -0
- package/yida-skills/skills/yida-data-management/references/api-matrix.md +49 -0
- package/yida-skills/skills/yida-data-management/references/data-format-guide.md +159 -0
- package/yida-skills/skills/yida-data-management/references/verified-endpoints.md +62 -0
- package/yida-skills/skills/yida-login/SKILL.md +159 -0
- package/yida-skills/skills/yida-logout/SKILL.md +67 -0
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { createYidaClient } = require('../core/yida-client');
|
|
4
|
+
const { t } = require('../core/i18n');
|
|
5
|
+
|
|
6
|
+
function resolveLocalizedText(value, fallback = '') {
|
|
7
|
+
if (!value) {
|
|
8
|
+
return fallback;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
if (typeof value === 'string') {
|
|
12
|
+
return value;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
if (typeof value === 'object') {
|
|
16
|
+
return value.zh_CN || value.en_US || value.zh_TW || fallback;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return fallback;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function normalizeFormNavigationNode(node) {
|
|
23
|
+
if (!node || node.navType === 'SYSTEM' || !node.formUuid) {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return {
|
|
28
|
+
formUuid: node.formUuid,
|
|
29
|
+
formName: resolveLocalizedText(node.title || node.i18nTitle || node.name, t('list_forms.unnamed_form')),
|
|
30
|
+
formType: node.formType || '',
|
|
31
|
+
pathName: node.pathName || '',
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async function fetchFormPageList(appType, authRef) {
|
|
36
|
+
const result = await createYidaClient({ authRef }).get(
|
|
37
|
+
`/dingtalk/web/${appType}/query/formnav/getFormNavigationListByOrder.json`,
|
|
38
|
+
{ _api: 'Nav.queryList', _mock: false }
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
if (!result || result.success === false) {
|
|
42
|
+
throw new Error(
|
|
43
|
+
t('list_forms.fetch_failed') + ': ' +
|
|
44
|
+
(result ? result.errorMsg || t('common.unknown_error') : t('common.request_failed'))
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const items = Array.isArray(result.content) ? result.content : [];
|
|
49
|
+
return items
|
|
50
|
+
.map(normalizeFormNavigationNode)
|
|
51
|
+
.filter(Boolean);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
module.exports = {
|
|
55
|
+
fetchFormPageList,
|
|
56
|
+
normalizeFormNavigationNode,
|
|
57
|
+
resolveLocalizedText,
|
|
58
|
+
};
|
|
@@ -0,0 +1,538 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* get-schema.js - 宜搭表单 Schema 获取命令
|
|
3
|
+
*
|
|
4
|
+
* 用法:
|
|
5
|
+
* yidaconnector get-schema <appType> <formUuid>
|
|
6
|
+
* yidaconnector get-schema <appType> --all [--output-dir <dir>] [--concurrency N] [--retries N]
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
'use strict';
|
|
10
|
+
|
|
11
|
+
const fs = require('fs');
|
|
12
|
+
const path = require('path');
|
|
13
|
+
const {
|
|
14
|
+
loadCookieData,
|
|
15
|
+
triggerLogin,
|
|
16
|
+
resolveBaseUrl,
|
|
17
|
+
httpGet,
|
|
18
|
+
requestWithAutoLogin,
|
|
19
|
+
} = require('../core/utils');
|
|
20
|
+
const { t } = require('../core/i18n');
|
|
21
|
+
const { fetchFormPageList } = require('./form-navigation');
|
|
22
|
+
|
|
23
|
+
// 需要在报表 fieldCode 中加 _value 后缀的字段类型
|
|
24
|
+
const FIELD_TYPES_NEEDING_VALUE_SUFFIX = new Set([
|
|
25
|
+
'SelectField',
|
|
26
|
+
'EmployeeField',
|
|
27
|
+
'RadioField',
|
|
28
|
+
'CheckboxField',
|
|
29
|
+
]);
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* 从 Schema 中提取字段摘要,列出每个字段的真实 fieldId、组件别名和报表用 reportFieldCode。
|
|
33
|
+
* @param {object} schemaResult - getFormSchema API 返回结果
|
|
34
|
+
* @returns {Array<{label, componentName, fieldId, alias, reportFieldCode}>}
|
|
35
|
+
*/
|
|
36
|
+
function extractFieldSummary(schemaResult) {
|
|
37
|
+
const fields = [];
|
|
38
|
+
const pages = schemaResult.content && schemaResult.content.pages;
|
|
39
|
+
if (!pages || pages.length === 0) {
|
|
40
|
+
return fields;
|
|
41
|
+
}
|
|
42
|
+
const aliasMaps = buildComponentAliasMaps(schemaResult);
|
|
43
|
+
|
|
44
|
+
const FIELD_COMPONENT_NAMES = new Set([
|
|
45
|
+
'TextField', 'TextareaField', 'SelectField', 'DateField', 'NumberField',
|
|
46
|
+
'RadioField', 'CheckboxField', 'EmployeeField', 'PhoneField', 'EmailField',
|
|
47
|
+
'CascadeSelectField', 'ImageField', 'AttachmentField', 'TableField',
|
|
48
|
+
]);
|
|
49
|
+
|
|
50
|
+
function traverse(node) {
|
|
51
|
+
if (!node) {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
if (FIELD_COMPONENT_NAMES.has(node.componentName)) {
|
|
55
|
+
const props = node.props || {};
|
|
56
|
+
const labelRaw = props.label;
|
|
57
|
+
const label = labelRaw
|
|
58
|
+
? (typeof labelRaw === 'object' ? (labelRaw.zh_CN || labelRaw.en_US || '') : String(labelRaw))
|
|
59
|
+
: '';
|
|
60
|
+
const fieldId = props.fieldId || '';
|
|
61
|
+
const reportFieldCode = FIELD_TYPES_NEEDING_VALUE_SUFFIX.has(node.componentName)
|
|
62
|
+
? `${fieldId}_value`
|
|
63
|
+
: fieldId;
|
|
64
|
+
if (fieldId) {
|
|
65
|
+
fields.push({
|
|
66
|
+
label,
|
|
67
|
+
componentName: node.componentName,
|
|
68
|
+
fieldId,
|
|
69
|
+
alias: aliasMaps.aliasByFieldId[fieldId] || '',
|
|
70
|
+
reportFieldCode,
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
if (node.children) {
|
|
75
|
+
node.children.forEach(traverse);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// 遍历所有页面,避免多页面表单遗漏字段
|
|
80
|
+
for (const page of pages) {
|
|
81
|
+
const tree = page.componentsTree && page.componentsTree[0];
|
|
82
|
+
if (tree) {
|
|
83
|
+
traverse(tree);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return fields;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function buildComponentAliasMaps(schemaResult) {
|
|
91
|
+
const aliasByFieldId = {};
|
|
92
|
+
const fieldIdByAlias = {};
|
|
93
|
+
const pages = schemaResult && schemaResult.content && schemaResult.content.pages;
|
|
94
|
+
if (!Array.isArray(pages)) {
|
|
95
|
+
return { aliasByFieldId, fieldIdByAlias };
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
pages.forEach((page) => {
|
|
99
|
+
const items = page &&
|
|
100
|
+
page.componentAlias &&
|
|
101
|
+
Array.isArray(page.componentAlias.items)
|
|
102
|
+
? page.componentAlias.items
|
|
103
|
+
: [];
|
|
104
|
+
items.forEach((item) => {
|
|
105
|
+
const fieldId = item && item.fieldId ? String(item.fieldId).trim() : '';
|
|
106
|
+
const alias = item && item.alias ? String(item.alias).trim() : '';
|
|
107
|
+
if (!fieldId || !alias) {
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
aliasByFieldId[fieldId] = alias;
|
|
111
|
+
fieldIdByAlias[alias] = fieldId;
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
return { aliasByFieldId, fieldIdByAlias };
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function parsePositiveInt(value, fallback, min, max) {
|
|
119
|
+
const parsed = Number.parseInt(value, 10);
|
|
120
|
+
if (!Number.isFinite(parsed) || parsed < min) {
|
|
121
|
+
return fallback;
|
|
122
|
+
}
|
|
123
|
+
return Math.min(parsed, max);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function parseArgs(args) {
|
|
127
|
+
const parsed = {
|
|
128
|
+
appType: args[0] || '',
|
|
129
|
+
formUuid: '',
|
|
130
|
+
all: false,
|
|
131
|
+
outputDir: '',
|
|
132
|
+
concurrency: 3,
|
|
133
|
+
retries: 1,
|
|
134
|
+
keyword: '',
|
|
135
|
+
// --field <labelOrFieldId>:单字段过滤模式,命中时跳过 runBatch 分支,
|
|
136
|
+
// 仅返回目标字段的 {componentName, fieldId, label, props}
|
|
137
|
+
field: '',
|
|
138
|
+
json: false,
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
for (let index = 1; index < args.length; index++) {
|
|
142
|
+
const arg = args[index];
|
|
143
|
+
if (arg === '--all') {
|
|
144
|
+
parsed.all = true;
|
|
145
|
+
} else if (arg === '--output-dir' && args[index + 1]) {
|
|
146
|
+
parsed.outputDir = args[index + 1];
|
|
147
|
+
index++;
|
|
148
|
+
} else if ((arg === '--concurrency' || arg === '--parallel') && args[index + 1]) {
|
|
149
|
+
parsed.concurrency = parsePositiveInt(args[index + 1], 3, 1, 10);
|
|
150
|
+
index++;
|
|
151
|
+
} else if ((arg === '--retries' || arg === '--retry') && args[index + 1]) {
|
|
152
|
+
parsed.retries = parsePositiveInt(args[index + 1], 1, 0, 5);
|
|
153
|
+
index++;
|
|
154
|
+
} else if (arg === '--keyword' && args[index + 1]) {
|
|
155
|
+
parsed.keyword = args[index + 1];
|
|
156
|
+
index++;
|
|
157
|
+
} else if (arg === '--field' && args[index + 1]) {
|
|
158
|
+
parsed.field = args[index + 1];
|
|
159
|
+
index++;
|
|
160
|
+
} else if (arg === '--json') {
|
|
161
|
+
parsed.json = true;
|
|
162
|
+
} else if (!arg.startsWith('--') && !parsed.formUuid) {
|
|
163
|
+
parsed.formUuid = arg;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return parsed;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function ensureUsage(parsed) {
|
|
171
|
+
if (!parsed.appType || (!parsed.all && !parsed.formUuid)) {
|
|
172
|
+
const { error: chalkError } = require('../core/chalk');
|
|
173
|
+
chalkError(t('get_schema.usage'), { hint: t('get_schema.example') });
|
|
174
|
+
}
|
|
175
|
+
// --field 模式必须同时提供 formUuid(单字段过滤不支持 --all 批量)
|
|
176
|
+
if (parsed.field && parsed.all) {
|
|
177
|
+
const { error: chalkError } = require('../core/chalk');
|
|
178
|
+
chalkError('--field 不支持与 --all 同时使用,请改为单 form 模式');
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* 从已解析好的字段列表中按 label 或 fieldId 精确匹配。
|
|
184
|
+
* 优先按 label 完整匹配(区分大小写),未命中再按 fieldId 完整匹配。
|
|
185
|
+
* 命中即返回首个,未命中返回 null。
|
|
186
|
+
*
|
|
187
|
+
* @param {Array<object>} fieldNodes - 来自 collectFieldNodes 的原始字段节点数组
|
|
188
|
+
* @param {string} keyword - label(如「优先级」)或 fieldId(如 selectField_qkm136vkr)
|
|
189
|
+
*/
|
|
190
|
+
function findFieldNode(fieldNodes, keyword, aliasByFieldId = {}) {
|
|
191
|
+
if (!keyword) {return null;}
|
|
192
|
+
const target = String(keyword).trim();
|
|
193
|
+
|
|
194
|
+
for (const node of fieldNodes) {
|
|
195
|
+
const props = node.props || {};
|
|
196
|
+
const labelRaw = props.label;
|
|
197
|
+
const label = labelRaw
|
|
198
|
+
? (typeof labelRaw === 'object' ? (labelRaw.zh_CN || labelRaw.en_US || '') : String(labelRaw))
|
|
199
|
+
: '';
|
|
200
|
+
if (label === target) {return node;}
|
|
201
|
+
}
|
|
202
|
+
for (const node of fieldNodes) {
|
|
203
|
+
const props = node.props || {};
|
|
204
|
+
if (props.fieldId && aliasByFieldId[props.fieldId] === target) {return node;}
|
|
205
|
+
}
|
|
206
|
+
for (const node of fieldNodes) {
|
|
207
|
+
const props = node.props || {};
|
|
208
|
+
if (props.fieldId === target) {return node;}
|
|
209
|
+
}
|
|
210
|
+
return null;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* 把 fetchSchema 的原始返回,递归收集到所有字段组件节点(含 props)。
|
|
215
|
+
* 与 extractFieldSummary 不同:保留 props 完整结构,便于 --field 模式输出。
|
|
216
|
+
*/
|
|
217
|
+
function collectFieldNodes(schemaResult) {
|
|
218
|
+
const nodes = [];
|
|
219
|
+
const pages = schemaResult && schemaResult.content && schemaResult.content.pages;
|
|
220
|
+
if (!pages || pages.length === 0) {return nodes;}
|
|
221
|
+
|
|
222
|
+
const FIELD_COMPONENT_NAMES = new Set([
|
|
223
|
+
'TextField', 'TextareaField', 'SelectField', 'DateField', 'NumberField',
|
|
224
|
+
'RadioField', 'CheckboxField', 'EmployeeField', 'PhoneField', 'EmailField',
|
|
225
|
+
'CascadeSelectField', 'ImageField', 'AttachmentField', 'TableField',
|
|
226
|
+
'MultiSelectField', 'DepartmentSelectField', 'AssociationFormField',
|
|
227
|
+
'CountrySelectField', 'CitySelectField', 'RateField', 'SignatureField',
|
|
228
|
+
'SerialNumberField',
|
|
229
|
+
]);
|
|
230
|
+
|
|
231
|
+
function traverse(node) {
|
|
232
|
+
if (!node) {return;}
|
|
233
|
+
if (FIELD_COMPONENT_NAMES.has(node.componentName)) {
|
|
234
|
+
nodes.push(node);
|
|
235
|
+
}
|
|
236
|
+
if (node.children) {
|
|
237
|
+
node.children.forEach(traverse);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
for (const page of pages) {
|
|
242
|
+
const tree = page.componentsTree && page.componentsTree[0];
|
|
243
|
+
if (tree) {traverse(tree);}
|
|
244
|
+
}
|
|
245
|
+
return nodes;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
function createAuthRef() {
|
|
249
|
+
const { step, info, success: chalkSuccess } = require('../core/chalk');
|
|
250
|
+
|
|
251
|
+
// Step 1: 读取登录态
|
|
252
|
+
step(1, t('common.step_login', 1));
|
|
253
|
+
let cookieData = loadCookieData();
|
|
254
|
+
if (!cookieData) {
|
|
255
|
+
info(t('common.login_no_cache'));
|
|
256
|
+
cookieData = triggerLogin();
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
const authRef = {
|
|
260
|
+
csrfToken: cookieData.csrf_token,
|
|
261
|
+
cookies: cookieData.cookies,
|
|
262
|
+
baseUrl: resolveBaseUrl(cookieData),
|
|
263
|
+
cookieData,
|
|
264
|
+
};
|
|
265
|
+
chalkSuccess(t('common.login_ready', authRef.baseUrl));
|
|
266
|
+
return authRef;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
async function fetchSchema(appType, formUuid, authRef) {
|
|
270
|
+
return requestWithAutoLogin((auth) => {
|
|
271
|
+
return httpGet(
|
|
272
|
+
auth.baseUrl,
|
|
273
|
+
`/alibaba/web/${appType}/_view/query/formdesign/getFormSchema.json`,
|
|
274
|
+
{ formUuid, schemaVersion: 'V5' },
|
|
275
|
+
auth.cookies
|
|
276
|
+
);
|
|
277
|
+
}, authRef);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
function isSuccessfulSchemaResult(result) {
|
|
281
|
+
return !!(result && result.success !== false && !result.__needLogin && !result.__csrfExpired);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
function printFieldSummary(result) {
|
|
285
|
+
const { c } = require('../core/chalk');
|
|
286
|
+
const fieldSummary = extractFieldSummary(result);
|
|
287
|
+
if (fieldSummary.length === 0) {
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
process.stderr.write(`\n ${c.bold}${c.cyan}📋 字段摘要${c.reset} ${c.dim}(报表配置请使用 reportFieldCode)${c.reset}\n`);
|
|
292
|
+
process.stderr.write(` ${c.dim}${'─'.repeat(80)}${c.reset}\n`);
|
|
293
|
+
process.stderr.write(
|
|
294
|
+
` ${c.bold}${'label'.padEnd(16)}${'alias'.padEnd(18)}${'componentName'.padEnd(20)}${'fieldId'.padEnd(28)}reportFieldCode${c.reset}\n`
|
|
295
|
+
);
|
|
296
|
+
process.stderr.write(` ${c.dim}${'─'.repeat(80)}${c.reset}\n`);
|
|
297
|
+
for (const field of fieldSummary) {
|
|
298
|
+
process.stderr.write(
|
|
299
|
+
` ${c.green}${field.label.padEnd(16)}${c.reset}${c.yellow}${(field.alias || '').padEnd(18)}${c.reset}${c.dim}${field.componentName.padEnd(20)}${c.reset}${field.fieldId.padEnd(28)}${c.cyan}${field.reportFieldCode}${c.reset}\n`
|
|
300
|
+
);
|
|
301
|
+
}
|
|
302
|
+
process.stderr.write(` ${c.dim}${'─'.repeat(80)}${c.reset}\n`);
|
|
303
|
+
process.stderr.write(` ${c.dim}注:SelectField/EmployeeField 在报表中需加 _value 后缀${c.reset}\n\n`);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
function filterForms(forms, keyword) {
|
|
307
|
+
if (!keyword) {
|
|
308
|
+
return forms;
|
|
309
|
+
}
|
|
310
|
+
const needle = keyword.toLowerCase();
|
|
311
|
+
return forms.filter((form) => {
|
|
312
|
+
return [form.formName, form.formUuid, form.formType, form.pathName]
|
|
313
|
+
.filter(Boolean)
|
|
314
|
+
.some((value) => String(value).toLowerCase().includes(needle));
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
function sanitizeFileName(value) {
|
|
319
|
+
return String(value || 'schema')
|
|
320
|
+
.replace(/[\\/:"*?<>|]+/g, '_')
|
|
321
|
+
.replace(/\s+/g, '_')
|
|
322
|
+
.slice(0, 160);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
async function mapLimit(items, limit, iterator) {
|
|
326
|
+
const results = new Array(items.length);
|
|
327
|
+
let cursor = 0;
|
|
328
|
+
|
|
329
|
+
async function worker() {
|
|
330
|
+
while (cursor < items.length) {
|
|
331
|
+
const current = cursor;
|
|
332
|
+
cursor++;
|
|
333
|
+
results[current] = await iterator(items[current], current);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
const workerCount = Math.min(Math.max(limit, 1), items.length);
|
|
338
|
+
await Promise.all(Array.from({ length: workerCount }, worker));
|
|
339
|
+
return results;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
async function fetchSchemaRecord(appType, form, authRef, retries) {
|
|
343
|
+
let lastError = null;
|
|
344
|
+
for (let attempt = 0; attempt <= retries; attempt++) {
|
|
345
|
+
try {
|
|
346
|
+
const result = await fetchSchema(appType, form.formUuid, authRef);
|
|
347
|
+
if (isSuccessfulSchemaResult(result)) {
|
|
348
|
+
return {
|
|
349
|
+
formUuid: form.formUuid,
|
|
350
|
+
formName: form.formName,
|
|
351
|
+
formType: form.formType,
|
|
352
|
+
pathName: form.pathName,
|
|
353
|
+
success: true,
|
|
354
|
+
attempts: attempt + 1,
|
|
355
|
+
fieldSummary: extractFieldSummary(result),
|
|
356
|
+
schema: result,
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
lastError = new Error(result ? result.errorMsg || t('common.unknown_error') : t('common.request_failed'));
|
|
360
|
+
} catch (error) {
|
|
361
|
+
lastError = error;
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
return {
|
|
366
|
+
formUuid: form.formUuid,
|
|
367
|
+
formName: form.formName,
|
|
368
|
+
formType: form.formType,
|
|
369
|
+
pathName: form.pathName,
|
|
370
|
+
success: false,
|
|
371
|
+
attempts: retries + 1,
|
|
372
|
+
errorMsg: lastError ? lastError.message : t('common.unknown_error'),
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
function writeBatchOutput(outputDir, records) {
|
|
377
|
+
if (!outputDir) {
|
|
378
|
+
return records;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
const resolvedDir = path.resolve(outputDir);
|
|
382
|
+
fs.mkdirSync(resolvedDir, { recursive: true });
|
|
383
|
+
|
|
384
|
+
const indexRecords = records.map((record) => {
|
|
385
|
+
if (!record.success) {
|
|
386
|
+
return record;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
const baseName = sanitizeFileName(`${record.formName || 'form'}-${record.formUuid}`);
|
|
390
|
+
const fileName = `${baseName}.json`;
|
|
391
|
+
const filePath = path.join(resolvedDir, fileName);
|
|
392
|
+
fs.writeFileSync(filePath, JSON.stringify(record.schema, null, 2), 'utf-8');
|
|
393
|
+
|
|
394
|
+
const { schema, ...summary } = record;
|
|
395
|
+
void schema;
|
|
396
|
+
return {
|
|
397
|
+
...summary,
|
|
398
|
+
schemaFile: filePath,
|
|
399
|
+
};
|
|
400
|
+
});
|
|
401
|
+
|
|
402
|
+
fs.writeFileSync(path.join(resolvedDir, 'index.json'), JSON.stringify(indexRecords, null, 2), 'utf-8');
|
|
403
|
+
return indexRecords;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
async function runSingle(parsed, authRef) {
|
|
407
|
+
const { banner, step, label, info, success: chalkSuccess, result: chalkResult } = require('../core/chalk');
|
|
408
|
+
|
|
409
|
+
banner(t('get_schema.title'));
|
|
410
|
+
label('App', parsed.appType);
|
|
411
|
+
label('Form', parsed.formUuid);
|
|
412
|
+
if (parsed.field) {label('Field', parsed.field);}
|
|
413
|
+
|
|
414
|
+
// Step 2: 获取表单 Schema
|
|
415
|
+
step(2, t('get_schema.step_get'));
|
|
416
|
+
info(t('get_schema.sending'));
|
|
417
|
+
|
|
418
|
+
const result = await fetchSchema(parsed.appType, parsed.formUuid, authRef);
|
|
419
|
+
|
|
420
|
+
if (!isSuccessfulSchemaResult(result)) {
|
|
421
|
+
const errorMsg = result ? result.errorMsg || t('common.unknown_error') : t('common.request_failed');
|
|
422
|
+
chalkResult(false, t('get_schema.failed', errorMsg));
|
|
423
|
+
process.exit(1);
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
// --field 模式:只返回单个字段的详细 props(含 SelectField.dataSource 等)
|
|
427
|
+
if (parsed.field) {
|
|
428
|
+
const allFieldNodes = collectFieldNodes(result);
|
|
429
|
+
const aliasMaps = buildComponentAliasMaps(result);
|
|
430
|
+
const matched = findFieldNode(allFieldNodes, parsed.field, aliasMaps.aliasByFieldId);
|
|
431
|
+
if (!matched) {
|
|
432
|
+
const { error: chalkError } = require('../core/chalk');
|
|
433
|
+
chalkError(`未找到字段:${parsed.field}`, {
|
|
434
|
+
hint: `共 ${allFieldNodes.length} 个字段,可用 yidaconnector get-schema ${parsed.appType} ${parsed.formUuid} 查看完整列表`,
|
|
435
|
+
});
|
|
436
|
+
return;
|
|
437
|
+
}
|
|
438
|
+
const props = matched.props || {};
|
|
439
|
+
const labelRaw = props.label;
|
|
440
|
+
const label = labelRaw
|
|
441
|
+
? (typeof labelRaw === 'object' ? (labelRaw.zh_CN || labelRaw.en_US || '') : String(labelRaw))
|
|
442
|
+
: '';
|
|
443
|
+
const compact = {
|
|
444
|
+
componentName: matched.componentName,
|
|
445
|
+
fieldId: props.fieldId || '',
|
|
446
|
+
alias: aliasMaps.aliasByFieldId[props.fieldId] || '',
|
|
447
|
+
label,
|
|
448
|
+
props,
|
|
449
|
+
};
|
|
450
|
+
chalkSuccess(t('get_schema.success'));
|
|
451
|
+
console.log(JSON.stringify(compact, null, 2));
|
|
452
|
+
return;
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
chalkSuccess(t('get_schema.success'));
|
|
456
|
+
printFieldSummary(result);
|
|
457
|
+
console.log(JSON.stringify(result, null, 2));
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
async function runBatch(parsed, authRef) {
|
|
461
|
+
const { banner, step, label, info, success: chalkSuccess, result: chalkResult } = require('../core/chalk');
|
|
462
|
+
|
|
463
|
+
banner(t('get_schema.title'));
|
|
464
|
+
label('App', parsed.appType);
|
|
465
|
+
label('Mode', 'all');
|
|
466
|
+
if (parsed.outputDir) {label('Output', parsed.outputDir);}
|
|
467
|
+
|
|
468
|
+
step(2, t('list_forms.step_get'));
|
|
469
|
+
const allForms = await fetchFormPageList(parsed.appType, authRef);
|
|
470
|
+
const forms = filterForms(allForms, parsed.keyword);
|
|
471
|
+
info(t('list_forms.found', forms.length));
|
|
472
|
+
|
|
473
|
+
if (forms.length === 0) {
|
|
474
|
+
console.log(JSON.stringify({
|
|
475
|
+
appType: parsed.appType,
|
|
476
|
+
total: 0,
|
|
477
|
+
successCount: 0,
|
|
478
|
+
failedCount: 0,
|
|
479
|
+
forms: [],
|
|
480
|
+
}, null, 2));
|
|
481
|
+
return;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
step(3, t('get_schema.step_get'));
|
|
485
|
+
info(` 批量获取 ${forms.length} 个表单 Schema,并发 ${parsed.concurrency},重试 ${parsed.retries}`);
|
|
486
|
+
|
|
487
|
+
const records = await mapLimit(forms, parsed.concurrency, async (form) => {
|
|
488
|
+
const record = await fetchSchemaRecord(parsed.appType, form, authRef, parsed.retries);
|
|
489
|
+
if (record.success) {
|
|
490
|
+
info(` ✅ ${record.formName || record.formUuid}`);
|
|
491
|
+
} else {
|
|
492
|
+
info(` ❌ ${record.formName || record.formUuid}: ${record.errorMsg}`);
|
|
493
|
+
}
|
|
494
|
+
return record;
|
|
495
|
+
});
|
|
496
|
+
|
|
497
|
+
const outputRecords = writeBatchOutput(parsed.outputDir, records);
|
|
498
|
+
const successCount = records.filter(record => record.success).length;
|
|
499
|
+
const failedCount = records.length - successCount;
|
|
500
|
+
|
|
501
|
+
if (failedCount === 0) {
|
|
502
|
+
chalkSuccess(t('get_schema.success'));
|
|
503
|
+
} else {
|
|
504
|
+
chalkResult(false, ` ⚠️ ${failedCount} 个 Schema 获取失败`);
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
console.log(JSON.stringify({
|
|
508
|
+
appType: parsed.appType,
|
|
509
|
+
total: records.length,
|
|
510
|
+
successCount,
|
|
511
|
+
failedCount,
|
|
512
|
+
outputDir: parsed.outputDir ? path.resolve(parsed.outputDir) : undefined,
|
|
513
|
+
forms: outputRecords,
|
|
514
|
+
}, null, 2));
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
async function run(args) {
|
|
518
|
+
const parsed = parseArgs(args);
|
|
519
|
+
ensureUsage(parsed);
|
|
520
|
+
const authRef = createAuthRef();
|
|
521
|
+
|
|
522
|
+
if (parsed.all) {
|
|
523
|
+
return runBatch(parsed, authRef);
|
|
524
|
+
}
|
|
525
|
+
return runSingle(parsed, authRef);
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
module.exports = {
|
|
529
|
+
extractFieldSummary,
|
|
530
|
+
buildComponentAliasMaps,
|
|
531
|
+
parseArgs,
|
|
532
|
+
filterForms,
|
|
533
|
+
mapLimit,
|
|
534
|
+
fetchSchemaRecord,
|
|
535
|
+
collectFieldNodes,
|
|
536
|
+
findFieldNode,
|
|
537
|
+
run,
|
|
538
|
+
};
|