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.
Files changed (79) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +383 -0
  3. package/bin/yida.js +670 -0
  4. package/lib/app/form-navigation.js +58 -0
  5. package/lib/app/get-schema.js +538 -0
  6. package/lib/auth/auth.js +294 -0
  7. package/lib/auth/cdp-browser-login.js +390 -0
  8. package/lib/auth/codex-login.js +71 -0
  9. package/lib/auth/login.js +475 -0
  10. package/lib/auth/org.js +363 -0
  11. package/lib/auth/qr-login.js +1563 -0
  12. package/lib/core/chalk.js +384 -0
  13. package/lib/core/check-update.js +82 -0
  14. package/lib/core/cli-error.js +39 -0
  15. package/lib/core/command-manifest.js +106 -0
  16. package/lib/core/env-cmd.js +545 -0
  17. package/lib/core/env-manager.js +601 -0
  18. package/lib/core/env.js +287 -0
  19. package/lib/core/i18n.js +177 -0
  20. package/lib/core/locales/ar.js +805 -0
  21. package/lib/core/locales/de.js +805 -0
  22. package/lib/core/locales/en.js +1623 -0
  23. package/lib/core/locales/es.js +805 -0
  24. package/lib/core/locales/fr.js +805 -0
  25. package/lib/core/locales/hi.js +805 -0
  26. package/lib/core/locales/ja.js +1197 -0
  27. package/lib/core/locales/ko.js +807 -0
  28. package/lib/core/locales/pt.js +805 -0
  29. package/lib/core/locales/vi.js +805 -0
  30. package/lib/core/locales/zh-HK.js +1233 -0
  31. package/lib/core/locales/zh.js +1584 -0
  32. package/lib/core/query-data.js +781 -0
  33. package/lib/core/redact.js +100 -0
  34. package/lib/core/utils.js +799 -0
  35. package/lib/core/yida-client.js +117 -0
  36. package/package.json +94 -0
  37. package/project/config.json +4 -0
  38. package/project/pages/src/demo-birthday-game.oyd.jsx +832 -0
  39. package/project/pages/src/demo-chip-insight.oyd.jsx +983 -0
  40. package/project/pages/src/demo-compat-smoke.oyd.jsx +58 -0
  41. package/project/pages/src/demo-crm-batch-entry.oyd.jsx +805 -0
  42. package/project/pages/src/demo-crm-dashboard.oyd.jsx +677 -0
  43. package/project/pages/src/demo-future-vision-2026.oyd.jsx +1102 -0
  44. package/project/pages/src/demo-ppt.oyd.jsx +1192 -0
  45. package/project/pages/src/demo-salary-calculator.oyd.jsx +904 -0
  46. package/project/pages/src/yidaconnector-knowledge-doc.oyd.jsx +1714 -0
  47. package/project/prd/demo-birthday-game.md +39 -0
  48. package/project/prd/demo-crm.md +463 -0
  49. package/project/prd/demo-dingtalk-ai-solution-center.md +425 -0
  50. package/project/prd/demo-future-vision-2026.md +78 -0
  51. package/project/prd/demo-salary-calculator.md +101 -0
  52. package/scripts/build-skills-package.js +406 -0
  53. package/scripts/check-syntax.js +59 -0
  54. package/scripts/demo-dws.sh +106 -0
  55. package/scripts/e2e-real/cleanup.js +67 -0
  56. package/scripts/e2e-real/fixtures/form-fields.json +18 -0
  57. package/scripts/e2e-real/full-runner.js +1566 -0
  58. package/scripts/e2e-real/runner.js +293 -0
  59. package/scripts/e2e-real/skill-coverage.js +115 -0
  60. package/scripts/generate-command-docs.js +109 -0
  61. package/scripts/nightly-smoke.js +134 -0
  62. package/scripts/postinstall.js +545 -0
  63. package/scripts/solution-center-runner.js +368 -0
  64. package/scripts/validate-ci.sh +50 -0
  65. package/scripts/validate-command-manifest.js +119 -0
  66. package/scripts/validate-package-size.js +78 -0
  67. package/scripts/validate-skills.js +247 -0
  68. package/scripts/validate-structure.js +66 -0
  69. package/yida-skills/SKILL.md +163 -0
  70. package/yida-skills/references/yida-api.md +1309 -0
  71. package/yida-skills/skills/large-file-write/SKILL.md +91 -0
  72. package/yida-skills/skills/large-file-write/references/write-patterns.md +149 -0
  73. package/yida-skills/skills/large-file-write/scripts/write.js +157 -0
  74. package/yida-skills/skills/yida-data-management/SKILL.md +252 -0
  75. package/yida-skills/skills/yida-data-management/references/api-matrix.md +49 -0
  76. package/yida-skills/skills/yida-data-management/references/data-format-guide.md +159 -0
  77. package/yida-skills/skills/yida-data-management/references/verified-endpoints.md +62 -0
  78. package/yida-skills/skills/yida-login/SKILL.md +159 -0
  79. package/yida-skills/skills/yida-logout/SKILL.md +67 -0
@@ -0,0 +1,781 @@
1
+ /**
2
+ * query-data.js - 宜搭统一数据管理命令
3
+ *
4
+ * 用法:
5
+ * yidaconnector data <action> <resource> [参数]
6
+ *
7
+ * 支持的操作:
8
+ * query form / get form / create form / update form / query subform
9
+ * query process / get process / create process / update process
10
+ * query operation-records / execute task / query tasks
11
+ */
12
+
13
+ 'use strict';
14
+
15
+ const fs = require('fs');
16
+ const path = require('path');
17
+ const { CliError } = require('./cli-error');
18
+ const { createAuthRef, createYidaClient } = require('./yida-client');
19
+
20
+ const { buildComponentAliasMaps } = require('../app/get-schema');
21
+
22
+ const USAGE = `yidaconnector data - Unified Yida data CLI
23
+
24
+ Usage:
25
+ yidaconnector data query form <appType> <formUuid> [--page N] [--size N] [--all] [--max-pages N] [--search-json JSON|--search-file .cache/yidaconnector/search.json] [--resolve-aliases] [--inst-id ID] [--no-hydrate-subforms]
26
+ yidaconnector data get form <appType> --inst-id <formInstId> [--form-uuid <formUuid>] [--no-hydrate-subforms]
27
+ yidaconnector data create form <appType> <formUuid> (--data-json <JSON>|--data-file .cache/yidaconnector/data.json) [--dept-id ID] [--resolve-aliases]
28
+ yidaconnector data update form <appType> --inst-id <formInstId> (--data-json <JSON>|--data-file .cache/yidaconnector/data.json) [--form-uuid <formUuid>] [--use-latest-version y] [--resolve-aliases]
29
+ yidaconnector data query subform <appType> <formUuid> --inst-id <formInstId> --table-field-id <fieldId|alias> [--page N] [--size N] [--resolve-aliases]
30
+
31
+ yidaconnector data query process <appType> <formUuid> [--page N] [--size N] [--search-json JSON|--search-file .cache/yidaconnector/search.json] [--resolve-aliases] [--task-id ID] [--instance-status STATUS] [--approved-result RESULT]
32
+ yidaconnector data get process <appType> --process-inst-id <processInstanceId>
33
+ yidaconnector data create process <appType> <formUuid> --process-code <processCode> (--data-json <JSON>|--data-file .cache/yidaconnector/data.json) [--dept-id ID] [--resolve-aliases]
34
+ yidaconnector data update process <appType> --process-inst-id <processInstanceId> (--data-json <JSON>|--data-file .cache/yidaconnector/data.json) [--form-uuid <formUuid>] [--resolve-aliases]
35
+ yidaconnector data query operation-records <appType> --process-inst-id <processInstanceId>
36
+ yidaconnector data execute task <appType> --task-id <taskId> --process-inst-id <processInstanceId> --out-result <AGREE|DISAGREE> --remark <text> [--data-json JSON|--data-file .cache/yidaconnector/data.json] [--form-uuid <formUuid>] [--resolve-aliases] [--no-execute-expressions y]
37
+
38
+ yidaconnector data query tasks <appType> --type <todo|done|submitted|cc> [--page N] [--size N] [--keyword TEXT] [--process-codes JSON] [--instance-status STATUS]
39
+
40
+ Add --resolve-aliases when JSON keys use Yida component aliases instead of field IDs.
41
+ `;
42
+
43
+ function fail(message) {
44
+ throw new CliError(message, {
45
+ code: 'DATA_ERROR',
46
+ usage: USAGE,
47
+ });
48
+ }
49
+
50
+ function parseError(message) {
51
+ throw new CliError(`参数校验失败:${message}`, {
52
+ code: 'DATA_INVALID_ARGUMENTS',
53
+ usage: USAGE,
54
+ });
55
+ }
56
+
57
+ function ensureSession() {
58
+ const authRef = createAuthRef();
59
+ if (!authRef.cookies || authRef.cookies.length === 0 || !authRef.csrfToken) {
60
+ fail('无法获取有效登录态或 CSRF Token');
61
+ }
62
+ return authRef;
63
+ }
64
+
65
+ function parseCliOptions(tokens) {
66
+ const positionals = [];
67
+ const options = {};
68
+
69
+ for (let i = 0; i < tokens.length; i += 1) {
70
+ const token = tokens[i];
71
+ if (token.startsWith('--')) {
72
+ const key = token.slice(2).replace(/-/g, '_');
73
+ const next = tokens[i + 1];
74
+ if (next && !next.startsWith('--')) {
75
+ options[key] = next;
76
+ i += 1;
77
+ } else {
78
+ options[key] = true;
79
+ }
80
+ } else {
81
+ positionals.push(token);
82
+ }
83
+ }
84
+
85
+ return { positionals, options };
86
+ }
87
+
88
+ function clampPageSize(options, defaultSize = 20) {
89
+ let size = Number.parseInt(options.size || `${defaultSize}`, 10);
90
+ let page = Number.parseInt(options.page || '1', 10);
91
+
92
+ if (!Number.isFinite(size) || size <= 0) {size = defaultSize;}
93
+ if (size > 100) {
94
+ console.warn(`⚠️ 警告:pageSize 最大值为 100,已自动将 ${size} 截断为 100。宜搭 API 不支持超过 100 的 pageSize,超过会导致 HTTP 500 错误。`);
95
+ size = 100;
96
+ }
97
+ if (!Number.isFinite(page) || page <= 0) {page = 1;}
98
+
99
+ options.size = size;
100
+ options.page = page;
101
+ }
102
+
103
+ function parsePositiveInt(value, defaultValue, label) {
104
+ const parsed = Number.parseInt(value || `${defaultValue}`, 10);
105
+ if (!Number.isFinite(parsed) || parsed <= 0) {
106
+ if (label) {
107
+ parseError(`${label} 必须是正整数`);
108
+ }
109
+ return defaultValue;
110
+ }
111
+ return parsed;
112
+ }
113
+
114
+ function requirePositionals(positionals, count, names) {
115
+ if (positionals.length < count) {
116
+ parseError(`缺少必填参数 ${names.join(' ')}`);
117
+ }
118
+ }
119
+
120
+ function requireOption(options, key, flagName) {
121
+ if (!options[key]) {
122
+ parseError(`缺少必填参数 ${flagName || `--${key.replace(/_/g, '-')}`}`);
123
+ }
124
+ }
125
+
126
+ function readJsonFileOption(filePath, label) {
127
+ const resolvedPath = path.resolve(filePath);
128
+ let content;
129
+ try {
130
+ content = fs.readFileSync(resolvedPath, 'utf-8');
131
+ } catch (err) {
132
+ parseError(`${label} 文件读取失败:${resolvedPath} (${err.message})`);
133
+ }
134
+
135
+ try {
136
+ return JSON.stringify(JSON.parse(content));
137
+ } catch (err) {
138
+ parseError(`${label} 文件必须是合法 JSON:${resolvedPath} (${err.message})`);
139
+ }
140
+ }
141
+
142
+ function readJsonOption(options, jsonKey, fileKey, label) {
143
+ const jsonValue = options[jsonKey];
144
+ const fileValue = options[fileKey];
145
+
146
+ if (jsonValue && fileValue) {
147
+ parseError(`${label} 不能同时使用 --${jsonKey.replace(/_/g, '-')} 和 --${fileKey.replace(/_/g, '-')}`);
148
+ }
149
+
150
+ if (fileValue) {
151
+ return readJsonFileOption(fileValue, label);
152
+ }
153
+
154
+ if (!jsonValue) {
155
+ return '';
156
+ }
157
+
158
+ try {
159
+ JSON.parse(jsonValue);
160
+ } catch {
161
+ parseError(`--${jsonKey.replace(/_/g, '-')} 参数必须是合法的 JSON 字符串`);
162
+ }
163
+
164
+ return jsonValue;
165
+ }
166
+
167
+ function requireJsonOption(options, jsonKey, fileKey, label) {
168
+ const value = readJsonOption(options, jsonKey, fileKey, label);
169
+ if (!value) {
170
+ parseError(`缺少必填参数 --${jsonKey.replace(/_/g, '-')} 或 --${fileKey.replace(/_/g, '-')}`);
171
+ }
172
+ return value;
173
+ }
174
+
175
+ function snakeToCamel(value) {
176
+ const parts = value.split('_');
177
+ return parts[0] + parts.slice(1).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join('');
178
+ }
179
+
180
+ function buildRequestParams(session, params) {
181
+ return {
182
+ _api: 'nattyFetch',
183
+ _mock: 'false',
184
+ _csrf_token: session.csrfToken,
185
+ _stamp: `${Date.now()}`,
186
+ ...params,
187
+ };
188
+ }
189
+
190
+ async function sendGet(session, appType, requestPath, params) {
191
+ return createYidaClient({ authRef: session }).get(
192
+ requestPath,
193
+ auth => buildRequestParams(auth, params)
194
+ );
195
+ }
196
+
197
+ async function sendPost(session, appType, requestPath, params) {
198
+ return createYidaClient({ authRef: session }).postForm(
199
+ requestPath,
200
+ auth => buildRequestParams(auth, params)
201
+ );
202
+ }
203
+
204
+ function shouldResolveAliases(options) {
205
+ return !!(options.resolve_aliases || options.resolve_alias || options.component_aliases);
206
+ }
207
+
208
+ function isPlainObject(value) {
209
+ return Object.prototype.toString.call(value) === '[object Object]';
210
+ }
211
+
212
+ function buildFieldInfoById(schemaResult) {
213
+ const fieldInfoById = {};
214
+ const pages = schemaResult && schemaResult.content && schemaResult.content.pages;
215
+ if (!Array.isArray(pages)) {
216
+ return fieldInfoById;
217
+ }
218
+
219
+ function traverse(node) {
220
+ if (!node || typeof node !== 'object') {return;}
221
+ const props = node.props || {};
222
+ if (props.fieldId) {
223
+ fieldInfoById[props.fieldId] = {
224
+ fieldId: props.fieldId,
225
+ componentName: node.componentName || '',
226
+ };
227
+ }
228
+ if (Array.isArray(node.children)) {
229
+ node.children.forEach(traverse);
230
+ }
231
+ }
232
+
233
+ pages.forEach((page) => {
234
+ const trees = page && Array.isArray(page.componentsTree) ? page.componentsTree : [];
235
+ trees.forEach(traverse);
236
+ });
237
+ return fieldInfoById;
238
+ }
239
+
240
+ async function fetchFormAliasContext(session, appType, formUuid) {
241
+ const result = await createYidaClient({ authRef: session }).get(
242
+ `/alibaba/web/${appType}/_view/query/formdesign/getFormSchema.json`,
243
+ { formUuid, schemaVersion: 'V5' }
244
+ );
245
+ if (!result || result.success === false || result.__needLogin || result.__csrfExpired) {
246
+ fail(`组件别名解析失败:${result ? result.errorMsg || '无法获取表单 Schema' : '无法获取表单 Schema'}`);
247
+ }
248
+ return {
249
+ ...buildComponentAliasMaps(result),
250
+ fieldInfoById: buildFieldInfoById(result),
251
+ };
252
+ }
253
+
254
+ async function resolveAliasContextIfNeeded(session, appType, formUuid, options, jsonValue) {
255
+ if (!shouldResolveAliases(options) || !jsonValue) {
256
+ return null;
257
+ }
258
+ if (!formUuid) {
259
+ parseError('--resolve-aliases 需要提供 formUuid;对 update/get/execute 类命令请额外传 --form-uuid <formUuid>');
260
+ }
261
+ return fetchFormAliasContext(session, appType, formUuid);
262
+ }
263
+
264
+ function resolveFieldRef(value, aliasContext) {
265
+ if (!aliasContext || typeof value !== 'string') {
266
+ return value;
267
+ }
268
+ return aliasContext.fieldIdByAlias[value] || value;
269
+ }
270
+
271
+ function translateSubformRows(value, aliasContext) {
272
+ if (!Array.isArray(value)) {
273
+ return value;
274
+ }
275
+ return value.map((row) => {
276
+ if (!isPlainObject(row)) {
277
+ return row;
278
+ }
279
+ return translateFormDataObject(row, aliasContext);
280
+ });
281
+ }
282
+
283
+ function translateFormDataObject(data, aliasContext) {
284
+ if (!isPlainObject(data)) {
285
+ return data;
286
+ }
287
+ const translated = {};
288
+ Object.entries(data).forEach(([key, value]) => {
289
+ const fieldId = resolveFieldRef(key, aliasContext);
290
+ const fieldInfo = aliasContext && aliasContext.fieldInfoById[fieldId];
291
+ translated[fieldId] = fieldInfo && fieldInfo.componentName === 'TableField'
292
+ ? translateSubformRows(value, aliasContext)
293
+ : value;
294
+ });
295
+ return translated;
296
+ }
297
+
298
+ function translateSearchValue(value, aliasContext) {
299
+ if (Array.isArray(value)) {
300
+ return value.map(item => translateSearchValue(item, aliasContext));
301
+ }
302
+ if (!isPlainObject(value)) {
303
+ return value;
304
+ }
305
+ const translated = {};
306
+ const fieldRefKeys = ['key', 'field', 'fieldId', 'fieldName', 'componentId', 'tableFieldId'];
307
+ const hasFieldRefKey = Object.keys(value).some(key => fieldRefKeys.indexOf(key) !== -1);
308
+ Object.entries(value).forEach(([key, childValue]) => {
309
+ const shouldTranslateValue = fieldRefKeys.indexOf(key) !== -1;
310
+ const translatedKey = hasFieldRefKey ? key : resolveFieldRef(key, aliasContext);
311
+ translated[translatedKey] = shouldTranslateValue
312
+ ? resolveFieldRef(childValue, aliasContext)
313
+ : translateSearchValue(childValue, aliasContext);
314
+ });
315
+ return translated;
316
+ }
317
+
318
+ function translateJsonWithAliases(jsonValue, aliasContext, translator) {
319
+ if (!aliasContext || !jsonValue) {
320
+ return jsonValue;
321
+ }
322
+ try {
323
+ return JSON.stringify(translator(JSON.parse(jsonValue), aliasContext));
324
+ } catch (err) {
325
+ parseError(`--resolve-aliases 只能处理合法 JSON:${err.message}`);
326
+ }
327
+ return jsonValue;
328
+ }
329
+
330
+ /**
331
+ * 归一化 searchFormDatas / searchFormDataIds 的返回结构。
332
+ * 宜搭 API 在不同场景下会返回两种结构:
333
+ * - 直接结构:{ success, data: [...], totalCount, currentPage }
334
+ * - 嵌套结构:{ success, content: { data: [...], totalCount, currentPage } }
335
+ * 统一将嵌套结构展开为直接结构,方便调用方使用。
336
+ */
337
+ function normalizeFormDatasResult(result) {
338
+ if (!result || !result.success) {return result;}
339
+
340
+ const hasTopLevelData = Array.isArray(result.data);
341
+ const hasNestedContent = result.content && (Array.isArray(result.content.data) || Array.isArray(result.content.dataList));
342
+
343
+ if (!hasTopLevelData && hasNestedContent) {
344
+ const content = result.content;
345
+ return {
346
+ ...result,
347
+ data: content.data || content.dataList || [],
348
+ totalCount: content.totalCount !== undefined ? content.totalCount : result.totalCount,
349
+ currentPage: content.currentPage !== undefined ? content.currentPage : result.currentPage,
350
+ content: undefined,
351
+ };
352
+ }
353
+
354
+ return result;
355
+ }
356
+
357
+ function getResultDataList(result) {
358
+ if (!result) {return [];}
359
+ if (Array.isArray(result.data)) {return result.data;}
360
+ if (result.content && Array.isArray(result.content.data)) {return result.content.data;}
361
+ if (result.content && Array.isArray(result.content.dataList)) {return result.content.dataList;}
362
+ return [];
363
+ }
364
+
365
+ function getResultTotalCount(result) {
366
+ if (!result) {return 0;}
367
+ if (result.totalCount !== undefined) {return Number(result.totalCount) || 0;}
368
+ if (result.content && result.content.totalCount !== undefined) {return Number(result.content.totalCount) || 0;}
369
+ return 0;
370
+ }
371
+
372
+ function getFormDataContainer(result) {
373
+ if (!result || !result.success) {return null;}
374
+ const candidates = [
375
+ result.content,
376
+ result.data,
377
+ result.content && result.content.data,
378
+ result.content && result.content.formData,
379
+ ];
380
+
381
+ for (const candidate of candidates) {
382
+ if (!candidate || typeof candidate !== 'object') {continue;}
383
+ if (candidate.formData && typeof candidate.formData === 'object') {
384
+ return candidate.formData;
385
+ }
386
+ }
387
+
388
+ return null;
389
+ }
390
+
391
+ function normalizeSubformResult(result) {
392
+ const normalized = normalizeFormDatasResult(result);
393
+ return {
394
+ ...normalized,
395
+ data: getResultDataList(normalized),
396
+ totalCount: getResultTotalCount(normalized),
397
+ };
398
+ }
399
+
400
+ async function fetchAllPages(fetchPage, options, defaultSize = 100) {
401
+ const pageSize = Math.min(parsePositiveInt(options.size, defaultSize), 100);
402
+ const maxPages = parsePositiveInt(options.max_pages, 1000);
403
+ const firstPage = parsePositiveInt(options.page, 1);
404
+ const allData = [];
405
+ let page = firstPage;
406
+ let lastResult = null;
407
+ let pagesFetched = 0;
408
+
409
+ while (pagesFetched < maxPages) {
410
+ const result = normalizeFormDatasResult(await fetchPage(page, pageSize));
411
+ lastResult = result;
412
+ const data = getResultDataList(result);
413
+ allData.push(...data);
414
+ pagesFetched++;
415
+
416
+ const totalCount = getResultTotalCount(result);
417
+ if (totalCount && allData.length >= totalCount) {break;}
418
+ if (!data.length || data.length < pageSize) {break;}
419
+ page++;
420
+ }
421
+
422
+ return {
423
+ ...(lastResult || { success: true }),
424
+ data: allData,
425
+ totalCount: getResultTotalCount(lastResult) || allData.length,
426
+ currentPage: firstPage,
427
+ pageSize,
428
+ pagesFetched,
429
+ };
430
+ }
431
+
432
+ async function fetchAllSubformRows(session, appType, formUuid, formInstId, tableFieldId, options = {}) {
433
+ const result = await fetchAllPages((page, size) => sendGet(session, appType, `/dingtalk/web/${appType}/v1/form/listTableDataByFormInstIdAndTableId.json`, {
434
+ formUuid,
435
+ formInstanceId: formInstId,
436
+ tableFieldId,
437
+ currentPage: String(page),
438
+ pageSize: String(size),
439
+ }), {
440
+ page: 1,
441
+ size: options.size || 100,
442
+ max_pages: options.max_pages || 1000,
443
+ }, 100);
444
+ return normalizeSubformResult(result);
445
+ }
446
+
447
+ async function hydrateTruncatedSubforms(result, context) {
448
+ if (!context || !context.formUuid || !context.formInstId || context.disabled) {
449
+ return result;
450
+ }
451
+
452
+ const formData = getFormDataContainer(result);
453
+ if (!formData) {return result;}
454
+
455
+ const hydrated = [];
456
+ const entries = Object.entries(formData);
457
+ for (const [fieldId, value] of entries) {
458
+ if (!/^tableField_/.test(fieldId)) {continue;}
459
+ if (!Array.isArray(value) || value.length < 50) {continue;}
460
+ if (value.length > 0 && !value.every(item => item && typeof item === 'object')) {continue;}
461
+
462
+ const subformResult = await fetchAllSubformRows(
463
+ context.session,
464
+ context.appType,
465
+ context.formUuid,
466
+ context.formInstId,
467
+ fieldId,
468
+ context.options || {}
469
+ );
470
+ const rows = subformResult.data || [];
471
+ if (rows.length > value.length) {
472
+ formData[fieldId] = rows;
473
+ hydrated.push({
474
+ fieldId,
475
+ originalCount: value.length,
476
+ hydratedCount: rows.length,
477
+ });
478
+ }
479
+ }
480
+
481
+ if (hydrated.length > 0) {
482
+ const target = result.content && typeof result.content === 'object' ? result.content : result;
483
+ target._yidaconnectorHydratedSubforms = hydrated;
484
+ }
485
+
486
+ return result;
487
+ }
488
+
489
+ function printResult(result) {
490
+ const errorCode = result && result.errorCode;
491
+ const hasErrorCode = errorCode !== undefined && errorCode !== null && errorCode !== '' && errorCode !== 0 && errorCode !== '0';
492
+
493
+ if (result && result.success && !hasErrorCode) {
494
+ console.log(JSON.stringify(result, null, 2));
495
+ return;
496
+ }
497
+
498
+ const payload = result || { success: false, errorMsg: '未知错误' };
499
+ throw new CliError(payload.errorMsg || '数据请求失败', {
500
+ code: 'DATA_API_ERROR',
501
+ details: payload,
502
+ });
503
+ }
504
+
505
+ function printFormDatasResult(result) {
506
+ printResult(normalizeFormDatasResult(result));
507
+ }
508
+
509
+ async function queryForm(positionals, options, session) {
510
+ requirePositionals(positionals, 2, ['appType', 'formUuid']);
511
+ const [appType, formUuid] = positionals;
512
+ clampPageSize(options, options.all ? 100 : 20);
513
+ const rawSearchJson = readJsonOption(options, 'search_json', 'search_file', '查询条件');
514
+ const aliasContext = await resolveAliasContextIfNeeded(session, appType, formUuid, options, rawSearchJson || options.dynamic_order);
515
+ const searchJson = translateJsonWithAliases(rawSearchJson, aliasContext, translateSearchValue);
516
+
517
+ let result;
518
+ if (options.inst_id) {
519
+ result = await sendGet(session, appType, `/dingtalk/web/${appType}/v1/form/getFormDataById.json`, {
520
+ formInstId: options.inst_id,
521
+ });
522
+ result = await hydrateTruncatedSubforms(result, {
523
+ session,
524
+ appType,
525
+ formUuid,
526
+ formInstId: options.inst_id,
527
+ disabled: !!options.no_hydrate_subforms,
528
+ options,
529
+ });
530
+ } else if (options.all) {
531
+ result = await fetchAllPages((page, size) => {
532
+ const params = {
533
+ formUuid,
534
+ appType,
535
+ currentPage: String(page),
536
+ pageSize: String(size),
537
+ };
538
+ if (searchJson) {
539
+ params.searchFieldJson = searchJson;
540
+ }
541
+ for (const key of ['originator_id', 'create_from', 'create_to', 'modified_from', 'modified_to', 'dynamic_order']) {
542
+ if (options[key]) {
543
+ params[snakeToCamel(key)] = key === 'dynamic_order' && aliasContext
544
+ ? translateJsonWithAliases(options[key], aliasContext, translateSearchValue)
545
+ : options[key];
546
+ }
547
+ }
548
+ const requestPath = options.ids_only
549
+ ? `/dingtalk/web/${appType}/v1/form/searchFormDataIds.json`
550
+ : `/dingtalk/web/${appType}/v1/form/searchFormDatas.json`;
551
+ return sendGet(session, appType, requestPath, params);
552
+ }, options, 100);
553
+ } else {
554
+ const params = {
555
+ formUuid,
556
+ appType,
557
+ currentPage: String(options.page),
558
+ pageSize: String(options.size),
559
+ };
560
+ if (searchJson) {
561
+ params.searchFieldJson = searchJson;
562
+ }
563
+ for (const key of ['originator_id', 'create_from', 'create_to', 'modified_from', 'modified_to', 'dynamic_order']) {
564
+ if (options[key]) {
565
+ params[snakeToCamel(key)] = key === 'dynamic_order' && aliasContext
566
+ ? translateJsonWithAliases(options[key], aliasContext, translateSearchValue)
567
+ : options[key];
568
+ }
569
+ }
570
+ const requestPath = options.ids_only
571
+ ? `/dingtalk/web/${appType}/v1/form/searchFormDataIds.json`
572
+ : `/dingtalk/web/${appType}/v1/form/searchFormDatas.json`;
573
+ result = await sendGet(session, appType, requestPath, params);
574
+ }
575
+
576
+ printFormDatasResult(result);
577
+ }
578
+
579
+ async function getForm(positionals, options, session) {
580
+ requirePositionals(positionals, 1, ['appType']);
581
+ requireOption(options, 'inst_id');
582
+ const [appType] = positionals;
583
+ const result = await sendGet(session, appType, `/dingtalk/web/${appType}/v1/form/getFormDataById.json`, {
584
+ formInstId: options.inst_id,
585
+ });
586
+ printResult(await hydrateTruncatedSubforms(result, {
587
+ session,
588
+ appType,
589
+ formUuid: options.form_uuid,
590
+ formInstId: options.inst_id,
591
+ disabled: !!options.no_hydrate_subforms,
592
+ options,
593
+ }));
594
+ }
595
+
596
+ async function createForm(positionals, options, session) {
597
+ requirePositionals(positionals, 2, ['appType', 'formUuid']);
598
+ const dataJson = requireJsonOption(options, 'data_json', 'data_file', '数据');
599
+ const [appType, formUuid] = positionals;
600
+ const aliasContext = await resolveAliasContextIfNeeded(session, appType, formUuid, options, dataJson);
601
+ const params = {
602
+ appType,
603
+ formUuid,
604
+ formDataJson: translateJsonWithAliases(dataJson, aliasContext, translateFormDataObject),
605
+ };
606
+ if (options.dept_id) {params.deptId = options.dept_id;}
607
+ printResult(await sendPost(session, appType, `/dingtalk/web/${appType}/v1/form/saveFormData.json`, params));
608
+ }
609
+
610
+ async function updateForm(positionals, options, session) {
611
+ requirePositionals(positionals, 1, ['appType']);
612
+ requireOption(options, 'inst_id');
613
+ const dataJson = requireJsonOption(options, 'data_json', 'data_file', '数据');
614
+ const [appType] = positionals;
615
+ const aliasContext = await resolveAliasContextIfNeeded(session, appType, options.form_uuid, options, dataJson);
616
+ const params = {
617
+ formInstId: options.inst_id,
618
+ updateFormDataJson: translateJsonWithAliases(dataJson, aliasContext, translateFormDataObject),
619
+ };
620
+ if (options.use_latest_version) {params.useLatestVersion = options.use_latest_version;}
621
+ printResult(await sendPost(session, appType, `/dingtalk/web/${appType}/v1/form/updateFormData.json`, params));
622
+ }
623
+
624
+ async function querySubform(positionals, options, session) {
625
+ requirePositionals(positionals, 2, ['appType', 'formUuid']);
626
+ requireOption(options, 'inst_id');
627
+ requireOption(options, 'table_field_id');
628
+ clampPageSize(options, 10);
629
+ const [appType, formUuid] = positionals;
630
+ const aliasContext = await resolveAliasContextIfNeeded(session, appType, formUuid, options, options.table_field_id);
631
+ const params = {
632
+ formUuid,
633
+ formInstanceId: options.inst_id,
634
+ tableFieldId: resolveFieldRef(options.table_field_id, aliasContext),
635
+ currentPage: String(options.page),
636
+ pageSize: String(options.size),
637
+ };
638
+ printResult(await sendGet(session, appType, `/dingtalk/web/${appType}/v1/form/listTableDataByFormInstIdAndTableId.json`, params));
639
+ }
640
+
641
+ async function queryProcess(positionals, options, session) {
642
+ requirePositionals(positionals, 2, ['appType', 'formUuid']);
643
+ clampPageSize(options, 10);
644
+ const [appType, formUuid] = positionals;
645
+ const params = {
646
+ formUuid,
647
+ currentPage: String(options.page),
648
+ pageSize: String(options.size),
649
+ };
650
+ const rawSearchJson = readJsonOption(options, 'search_json', 'search_file', '查询条件');
651
+ const aliasContext = await resolveAliasContextIfNeeded(session, appType, formUuid, options, rawSearchJson);
652
+ const searchJson = translateJsonWithAliases(rawSearchJson, aliasContext, translateSearchValue);
653
+ if (searchJson) {
654
+ params.searchFieldJson = searchJson;
655
+ }
656
+ for (const key of ['task_id', 'instance_status', 'approved_result', 'originator_id', 'create_from', 'create_to', 'modified_from', 'modified_to']) {
657
+ if (options[key]) {params[snakeToCamel(key)] = options[key];}
658
+ }
659
+ const requestPath = options.ids_only
660
+ ? `/dingtalk/web/${appType}/v1/process/getInstanceIds.json`
661
+ : `/dingtalk/web/${appType}/v1/process/getInstances.json`;
662
+ printResult(await sendGet(session, appType, requestPath, params));
663
+ }
664
+
665
+ async function getProcess(positionals, options, session) {
666
+ requirePositionals(positionals, 1, ['appType']);
667
+ requireOption(options, 'process_inst_id');
668
+ const [appType] = positionals;
669
+ printResult(await sendGet(session, appType, `/dingtalk/web/${appType}/v1/process/getInstanceById.json`, {
670
+ processInstanceId: options.process_inst_id,
671
+ }));
672
+ }
673
+
674
+ async function createProcess(positionals, options, session) {
675
+ requirePositionals(positionals, 2, ['appType', 'formUuid']);
676
+ requireOption(options, 'process_code');
677
+ const dataJson = requireJsonOption(options, 'data_json', 'data_file', '数据');
678
+ const [appType, formUuid] = positionals;
679
+ const aliasContext = await resolveAliasContextIfNeeded(session, appType, formUuid, options, dataJson);
680
+ const params = {
681
+ processCode: options.process_code,
682
+ formUuid,
683
+ formDataJson: translateJsonWithAliases(dataJson, aliasContext, translateFormDataObject),
684
+ };
685
+ if (options.dept_id) {params.deptId = options.dept_id;}
686
+ printResult(await sendPost(session, appType, `/dingtalk/web/${appType}/v1/process/startInstance.json`, params));
687
+ }
688
+
689
+ async function updateProcess(positionals, options, session) {
690
+ requirePositionals(positionals, 1, ['appType']);
691
+ requireOption(options, 'process_inst_id');
692
+ const dataJson = requireJsonOption(options, 'data_json', 'data_file', '数据');
693
+ const [appType] = positionals;
694
+ const aliasContext = await resolveAliasContextIfNeeded(session, appType, options.form_uuid, options, dataJson);
695
+ printResult(await sendPost(session, appType, `/dingtalk/web/${appType}/v1/process/updateInstance.json`, {
696
+ processInstanceId: options.process_inst_id,
697
+ updateFormDataJson: translateJsonWithAliases(dataJson, aliasContext, translateFormDataObject),
698
+ }));
699
+ }
700
+
701
+ async function queryOperationRecords(positionals, options, session) {
702
+ requirePositionals(positionals, 1, ['appType']);
703
+ requireOption(options, 'process_inst_id');
704
+ const [appType] = positionals;
705
+ printResult(await sendGet(session, appType, `/dingtalk/web/${appType}/v1/process/getOperationRecords.json`, {
706
+ processInstanceId: options.process_inst_id,
707
+ }));
708
+ }
709
+
710
+ async function executeTask(positionals, options, session) {
711
+ requirePositionals(positionals, 1, ['appType']);
712
+ for (const key of ['task_id', 'process_inst_id', 'out_result', 'remark']) {
713
+ requireOption(options, key);
714
+ }
715
+ const [appType] = positionals;
716
+ const params = {
717
+ taskId: options.task_id,
718
+ procInstId: options.process_inst_id,
719
+ outResult: options.out_result,
720
+ remark: options.remark,
721
+ };
722
+ const dataJson = readJsonOption(options, 'data_json', 'data_file', '数据');
723
+ const aliasContext = await resolveAliasContextIfNeeded(session, appType, options.form_uuid, options, dataJson);
724
+ if (dataJson) {params.formDataJson = translateJsonWithAliases(dataJson, aliasContext, translateFormDataObject);}
725
+ if (options.no_execute_expressions) {params.noExecuteExpressions = options.no_execute_expressions;}
726
+ printResult(await sendPost(session, appType, `/dingtalk/web/${appType}/v1/task/executeTask.json`, params));
727
+ }
728
+
729
+ async function queryTasks(positionals, options, session) {
730
+ requirePositionals(positionals, 1, ['appType']);
731
+ requireOption(options, 'type');
732
+ clampPageSize(options, 10);
733
+ const [appType] = positionals;
734
+ const typeMap = {
735
+ todo: 'task/getTodoTasksInApp',
736
+ done: 'task/getDoneTasksInApp',
737
+ submitted: 'process/getMySubmitInApp',
738
+ cc: 'task/getNotifyMeTasksInApp',
739
+ };
740
+ const endpoint = typeMap[options.type];
741
+ if (!endpoint) {
742
+ parseError('--type 仅支持 todo|done|submitted|cc');
743
+ }
744
+
745
+ const params = {
746
+ currentPage: String(options.page),
747
+ pageSize: String(options.size),
748
+ };
749
+ if (options.keyword) {params.keyword = options.keyword;}
750
+ if (options.process_codes) {params.processCodes = options.process_codes;}
751
+ if (options.instance_status) {params.instanceStatus = options.instance_status;}
752
+ printResult(await sendGet(session, appType, `/dingtalk/web/${appType}/v1/${endpoint}.json`, params));
753
+ }
754
+
755
+ async function run(args) {
756
+ if (args.length < 2) {
757
+ parseError('缺少必填参数 action 或 resource');
758
+ }
759
+
760
+ const action = args[0];
761
+ const resource = args[1];
762
+ const { positionals, options } = parseCliOptions(args.slice(2));
763
+ const session = ensureSession();
764
+
765
+ if (action === 'query' && resource === 'form') {return queryForm(positionals, options, session);}
766
+ if (action === 'get' && resource === 'form') {return getForm(positionals, options, session);}
767
+ if (action === 'create' && resource === 'form') {return createForm(positionals, options, session);}
768
+ if (action === 'update' && resource === 'form') {return updateForm(positionals, options, session);}
769
+ if (action === 'query' && resource === 'subform') {return querySubform(positionals, options, session);}
770
+ if (action === 'query' && resource === 'process') {return queryProcess(positionals, options, session);}
771
+ if (action === 'get' && resource === 'process') {return getProcess(positionals, options, session);}
772
+ if (action === 'create' && resource === 'process') {return createProcess(positionals, options, session);}
773
+ if (action === 'update' && resource === 'process') {return updateProcess(positionals, options, session);}
774
+ if (action === 'query' && resource === 'operation-records') {return queryOperationRecords(positionals, options, session);}
775
+ if (action === 'execute' && resource === 'task') {return executeTask(positionals, options, session);}
776
+ if (action === 'query' && resource === 'tasks') {return queryTasks(positionals, options, session);}
777
+
778
+ fail(`暂未实现的命令:${action} ${resource}`);
779
+ }
780
+
781
+ module.exports = { run };