openyida 2026.8.20 → 2026.8.25-1

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 (39) hide show
  1. package/README.md +1 -1
  2. package/lib/aggregate-table/aggregate-table.js +37 -14
  3. package/lib/aggregate-table/contract.js +234 -0
  4. package/lib/app-permission/app-permission.js +25 -6
  5. package/lib/connector/api.js +144 -36
  6. package/lib/connector/connector-create.js +4 -4
  7. package/lib/connector/connector-delete.js +7 -10
  8. package/lib/connector/connector-test.js +3 -2
  9. package/lib/connector/contract.js +98 -0
  10. package/lib/core/command-manifest.js +1 -1
  11. package/lib/core/locales/en.js +56 -2
  12. package/lib/core/locales/zh.js +56 -2
  13. package/lib/core/yida-client.js +19 -0
  14. package/lib/corp-manager/api.js +15 -3
  15. package/lib/corp-manager/corp-manager.js +34 -4
  16. package/lib/integration/connector-presets.js +2 -3
  17. package/lib/integration/integration-create.js +161 -80
  18. package/lib/integration/integration-process-builder.js +177 -48
  19. package/lib/integration/integration-spec-builder.js +171 -48
  20. package/lib/integration/integration-view-builder.js +26 -25
  21. package/lib/page-config/save-share-config.js +111 -10
  22. package/lib/permission/get-permission.js +15 -0
  23. package/lib/permission/save-permission.js +361 -79
  24. package/lib/process/configure-process.js +114 -71
  25. package/lib/process/create-process.js +4 -1
  26. package/lib/process/services/process-compiler.js +53 -28
  27. package/lib/report/append.js +18 -1
  28. package/lib/report/contract.js +158 -0
  29. package/lib/report/http.js +28 -5
  30. package/lib/report/index.js +23 -1
  31. package/package.json +5 -1
  32. package/scripts/i18n-baseline.json +45 -23
  33. package/scripts/validate-i18n.js +45 -15
  34. package/yida-skills/skills/yida-app-permission/SKILL.md +39 -47
  35. package/yida-skills/skills/yida-connector/SKILL.md +6 -6
  36. package/yida-skills/skills/yida-corp-manager/SKILL.md +45 -34
  37. package/yida-skills/skills/yida-form-permission/SKILL.md +108 -139
  38. package/yida-skills/skills/yida-integration/references/integration-node-schemas.md +2 -2
  39. package/yida-skills/skills/yida-page-config/SKILL.md +54 -79
package/README.md CHANGED
@@ -461,7 +461,7 @@ Run `openyida --help` or `openyida <command> --help` for detailed usage.
461
461
  | `openyida connector list` | List HTTP connectors |
462
462
  | `openyida connector create "name" "domain" ...` | Create a connector |
463
463
  | `openyida connector detail <id>` | View connector details |
464
- | `openyida connector delete <id>` | Delete a connector |
464
+ | `openyida connector delete <id> [--force]` | Show manual deletion guidance (CLI does not delete) |
465
465
  | `openyida connector add-action --operations <file> --connector-id <id>` | Add an action |
466
466
  | `openyida connector list-actions <id>` | List actions |
467
467
  | `openyida connector delete-action <id> <operation-id>` | Delete an action |
@@ -8,16 +8,13 @@ const { t } = require('../core/i18n');
8
8
  const { buildYidaTitleI18n, normalizeYidaLocale, resolveContentLocale } = require('../core/yida-i18n');
9
9
  const { parseOpenOption, withBrowserHandoff } = require('../core/browser-handoff');
10
10
  const { fetchFormPageList, resolveLocalizedText } = require('../app/form-navigation');
11
+ const {
12
+ DESIGN_KEYS,
13
+ assertAggregateDesignConfig,
14
+ assertAggregateDesignReadback,
15
+ } = require('./contract');
11
16
 
12
17
  const FORM_TYPE_VIRTUAL_VIEW = 'virtualView';
13
- const DESIGN_KEYS = [
14
- 'relationForms',
15
- 'relationships',
16
- 'aggregatedFields',
17
- 'auxFields',
18
- 'formulaFields',
19
- 'validators',
20
- ];
21
18
 
22
19
  function hasHelpFlag(args) {
23
20
  return (args || []).includes('--help') || (args || []).includes('-h');
@@ -133,7 +130,7 @@ async function checkVirtualViewFeature(authRef, appType) {
133
130
  }
134
131
 
135
132
  async function createEmptyVirtualView(authRef, appType, title) {
136
- return createYidaClient({ authRef }).postForm(
133
+ return createYidaClient({ authRef }).postFormOnce(
137
134
  `/dingtalk/web/${appType}/query/formdesign/saveFormSchemaInfo.json`,
138
135
  buildCreateEmptyPostData(title)
139
136
  );
@@ -157,7 +154,11 @@ async function postVirtualViewDesign(authRef, appType, formUuid, designInfo, act
157
154
  throw new Error(`Unknown aggregate table action: ${action}`);
158
155
  }
159
156
 
160
- return createYidaClient({ authRef }).postForm(
157
+ const client = createYidaClient({ authRef });
158
+ const post = action === 'save' || action === 'publish'
159
+ ? client.postFormOnce.bind(client)
160
+ : client.postForm.bind(client);
161
+ return post(
161
162
  `/alibaba/web/${appType}/query/virtualview/${endpoint}`,
162
163
  buildDesignPostData(formUuid, designInfo, gmtModified)
163
164
  );
@@ -318,6 +319,11 @@ async function runCreateEmpty(args) {
318
319
  const createResult = await createEmptyVirtualView(authRef, appType, name);
319
320
  const created = assertSuccess(createResult, t('aggregate_table.create_empty'));
320
321
  const formUuid = (created && created.formUuid) || created;
322
+ if ((typeof formUuid !== 'string' && typeof formUuid !== 'number') || String(formUuid).length === 0) {
323
+ const error = new Error('Aggregate table creation returned no recoverable identity.');
324
+ error.code = 'AGGREGATE_CREATE_IDENTITY_MISSING';
325
+ throw error;
326
+ }
321
327
  const designUrl = buildDesignUrl(authRef.baseUrl, appType, formUuid, { fromNew: true });
322
328
  const workbenchUrl = buildWorkbenchUrl(authRef.baseUrl, appType, formUuid);
323
329
 
@@ -401,6 +407,7 @@ async function runPreview(args) {
401
407
 
402
408
  const authRef = await createAuthRef();
403
409
  const designInfo = normalizeDesignConfig(readJsonInput(input), formUuid);
410
+ assertAggregateDesignConfig(designInfo, { mode: 'draft' });
404
411
  const result = await postVirtualViewDesign(authRef, appType, formUuid, designInfo, 'preview');
405
412
  const rows = assertSuccess(result, t('aggregate_table.preview'));
406
413
  const rowCount = Array.isArray(rows) ? rows.length : 0;
@@ -439,13 +446,28 @@ async function runSaveOrPublish(args, action) {
439
446
  }
440
447
 
441
448
  const authRef = await createAuthRef();
442
- const { designInfo, gmtModified } = await loadDesignForMutation(authRef, appType, formUuid, input, action);
443
- if (isPublish && designInfo.relationForms.length === 0) {
444
- throw new Error(t('aggregate_table.publish_requires_source'));
445
- }
449
+ const { designInfo, currentConfig, gmtModified } = await loadDesignForMutation(authRef, appType, formUuid, input, action);
450
+ assertAggregateDesignConfig(designInfo, { mode: isPublish ? 'publish' : 'draft' });
446
451
 
447
452
  const result = await postVirtualViewDesign(authRef, appType, formUuid, designInfo, action, gmtModified);
448
453
  const content = assertSuccess(result, isPublish ? t('aggregate_table.publish') : t('aggregate_table.save'));
454
+ const readbackResult = await getVirtualViewConfig(authRef, appType, formUuid);
455
+ const readback = assertSuccess(readbackResult, t('aggregate_table.inspect'));
456
+ assertAggregateDesignReadback(designInfo, readback);
457
+ const responseRevision = content && content.gmtModified;
458
+ const readbackRevision = readback && readback.gmtModified;
459
+ if ((responseRevision === undefined || responseRevision === null) &&
460
+ (readbackRevision === undefined || readbackRevision === null)) {
461
+ const error = new Error('AGGREGATE_WRITE_REVISION_MISSING');
462
+ error.code = 'AGGREGATE_WRITE_REVISION_MISSING';
463
+ throw error;
464
+ }
465
+ if ((responseRevision === undefined || responseRevision === null) &&
466
+ currentConfig && String(readbackRevision) === String(currentConfig.gmtModified)) {
467
+ const error = new Error('AGGREGATE_WRITE_REVISION_UNCHANGED');
468
+ error.code = 'AGGREGATE_WRITE_REVISION_UNCHANGED';
469
+ throw error;
470
+ }
449
471
  const designUrl = buildDesignUrl(authRef.baseUrl, appType, formUuid);
450
472
  const workbenchUrl = buildWorkbenchUrl(authRef.baseUrl, appType, formUuid);
451
473
 
@@ -465,6 +487,7 @@ async function runSaveOrPublish(args, action) {
465
487
  formUuid,
466
488
  gmtModified: content && content.gmtModified,
467
489
  response: content,
490
+ readbackVerified: true,
468
491
  designUrl,
469
492
  workbenchUrl,
470
493
  }, designUrl, { stage: `aggregate_table_${action}_success`, title: formUuid }, flags.openMode), null, 2));
@@ -0,0 +1,234 @@
1
+ 'use strict';
2
+
3
+ const { isDeepStrictEqual } = require('util');
4
+
5
+ const DESIGN_KEYS = [
6
+ 'relationForms',
7
+ 'relationships',
8
+ 'aggregatedFields',
9
+ 'auxFields',
10
+ 'formulaFields',
11
+ 'validators',
12
+ ];
13
+
14
+ // Mirrors the designer defaults read from configLimit. Only limits that map to
15
+ // persisted design arrays are enforced here.
16
+ const DEFAULT_LIMITS = Object.freeze({
17
+ relationForms: 10,
18
+ relationships: 10,
19
+ formulaFields: 10,
20
+ auxFields: 10,
21
+ });
22
+
23
+ function addError(errors, code, path) {
24
+ errors.push({ code, path });
25
+ }
26
+
27
+ function isNonEmptyValue(value) {
28
+ return value !== null && value !== undefined && String(value).length > 0;
29
+ }
30
+
31
+ function validateArrayShapes(config, errors) {
32
+ for (const key of DESIGN_KEYS) {
33
+ if (!Array.isArray(config && config[key])) {
34
+ addError(errors, 'AGGREGATE_DESIGN_ARRAY_REQUIRED', key);
35
+ }
36
+ }
37
+ }
38
+
39
+ function validateLimits(config, limits, errors) {
40
+ const limitCodes = {
41
+ relationForms: 'AGGREGATE_RELATION_FORMS_LIMIT',
42
+ relationships: 'AGGREGATE_RELATIONSHIPS_LIMIT',
43
+ formulaFields: 'AGGREGATE_FORMULA_FIELDS_LIMIT',
44
+ auxFields: 'AGGREGATE_AUX_FIELDS_LIMIT',
45
+ };
46
+
47
+ for (const [key, limit] of Object.entries(limits)) {
48
+ if (Array.isArray(config[key]) && config[key].length > limit) {
49
+ addError(errors, limitCodes[key] || 'AGGREGATE_DESIGN_LIMIT', key);
50
+ }
51
+ }
52
+ }
53
+
54
+ function validateEntries(config, errors) {
55
+ if (Array.isArray(config.relationForms)) {
56
+ config.relationForms.forEach((item, index) => {
57
+ if (!item || !isNonEmptyValue(item.formUuid)) {
58
+ addError(errors, 'AGGREGATE_RELATION_FORM_INVALID', `relationForms[${index}]`);
59
+ }
60
+ });
61
+ }
62
+
63
+ if (Array.isArray(config.relationships)) {
64
+ config.relationships.forEach((item, index) => {
65
+ const relationshipInfos = item && item.relationshipInfos;
66
+ if (!Array.isArray(relationshipInfos) || relationshipInfos.length === 0 ||
67
+ relationshipInfos.some((entry) => entry === null || entry === undefined)) {
68
+ addError(errors, 'AGGREGATE_RELATIONSHIP_INVALID', `relationships[${index}]`);
69
+ }
70
+ });
71
+ }
72
+
73
+ if (Array.isArray(config.aggregatedFields)) {
74
+ config.aggregatedFields.forEach((item, index) => {
75
+ if (!item || !isNonEmptyValue(item.name)) {
76
+ addError(errors, 'AGGREGATE_COLUMN_INVALID', `aggregatedFields[${index}]`);
77
+ }
78
+ });
79
+ }
80
+
81
+ if (Array.isArray(config.formulaFields)) {
82
+ config.formulaFields.forEach((item, index) => {
83
+ if (!item || !isNonEmptyValue(item.formula)) {
84
+ addError(errors, 'AGGREGATE_FORMULA_FIELD_INVALID', `formulaFields[${index}]`);
85
+ }
86
+ });
87
+ }
88
+
89
+ if (Array.isArray(config.validators)) {
90
+ config.validators.forEach((item, index) => {
91
+ if (!item || !isNonEmptyValue(item.formula)) {
92
+ addError(errors, 'AGGREGATE_VALIDATOR_INVALID', `validators[${index}]`);
93
+ }
94
+ });
95
+ }
96
+ }
97
+
98
+ function validatePublishCompleteness(config, errors) {
99
+ const required = [
100
+ ['relationForms', 'AGGREGATE_RELATION_FORMS_REQUIRED'],
101
+ ['relationships', 'AGGREGATE_RELATIONSHIPS_REQUIRED'],
102
+ ['aggregatedFields', 'AGGREGATE_COLUMNS_REQUIRED'],
103
+ ['formulaFields', 'AGGREGATE_FORMULA_FIELDS_REQUIRED'],
104
+ ];
105
+
106
+ for (const [key, code] of required) {
107
+ if (!Array.isArray(config[key]) || config[key].length === 0) {
108
+ addError(errors, code, key);
109
+ }
110
+ }
111
+ }
112
+
113
+ function validateAggregateDesignConfig(config, options = {}) {
114
+ const mode = options.mode || 'draft';
115
+ if (mode !== 'draft' && mode !== 'publish') {
116
+ throw new Error(`Unsupported aggregate contract mode: ${mode}`);
117
+ }
118
+
119
+ const safeConfig = config && typeof config === 'object' && !Array.isArray(config)
120
+ ? config
121
+ : {};
122
+ const limits = { ...DEFAULT_LIMITS, ...(options.limits || {}) };
123
+ const errors = [];
124
+
125
+ validateArrayShapes(safeConfig, errors);
126
+ validateLimits(safeConfig, limits, errors);
127
+ validateEntries(safeConfig, errors);
128
+ if (mode === 'publish') {
129
+ validatePublishCompleteness(safeConfig, errors);
130
+ }
131
+
132
+ return { valid: errors.length === 0, errors };
133
+ }
134
+
135
+ function assertAggregateDesignConfig(config, options = {}) {
136
+ const result = validateAggregateDesignConfig(config, options);
137
+ if (result.valid) {
138
+ return config;
139
+ }
140
+
141
+ const error = new Error(result.errors.map((item) => item.code).join(', '));
142
+ error.name = 'AggregateDesignContractError';
143
+ error.code = 'AGGREGATE_DESIGN_CONTRACT_INVALID';
144
+ error.details = result.errors;
145
+ throw error;
146
+ }
147
+
148
+ function projectAggregateDesignConfig(config) {
149
+ const safeConfig = config && typeof config === 'object' ? config : {};
150
+ const projected = {};
151
+ for (const key of DESIGN_KEYS) {
152
+ projected[key] = Array.isArray(safeConfig[key]) ? safeConfig[key] : [];
153
+ }
154
+ return projected;
155
+ }
156
+
157
+ function isI18nValue(value) {
158
+ return value && typeof value === 'object' && !Array.isArray(value) && (
159
+ value.type === 'i18n' ||
160
+ Object.prototype.hasOwnProperty.call(value, 'zh_CN') ||
161
+ Object.prototype.hasOwnProperty.call(value, 'en_US') ||
162
+ Object.prototype.hasOwnProperty.call(value, 'pureEn_US')
163
+ );
164
+ }
165
+
166
+ function canonicalizeI18nValue(value) {
167
+ const zhCN = value.zh_CN || value.value || '';
168
+ const enUS = value.en_US || value.pureEn_US || zhCN;
169
+ return {
170
+ type: value.type || 'i18n',
171
+ zh_CN: String(zhCN),
172
+ en_US: String(enUS),
173
+ };
174
+ }
175
+
176
+ function isEmptyFilter(value) {
177
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
178
+ return false;
179
+ }
180
+ const rules = value.rules;
181
+ return (rules === undefined || rules === null || (Array.isArray(rules) && rules.length === 0)) &&
182
+ !value.logicOperator;
183
+ }
184
+
185
+ function canonicalizeAggregateValue(value, key = '') {
186
+ if (isI18nValue(value)) {
187
+ return canonicalizeI18nValue(value);
188
+ }
189
+ if (key === 'filter' && isEmptyFilter(value)) {
190
+ return {};
191
+ }
192
+ if (Array.isArray(value)) {
193
+ return value.map((item) => canonicalizeAggregateValue(item));
194
+ }
195
+ if (!value || typeof value !== 'object') {
196
+ if (key === 'precision' && value !== undefined && value !== null && value !== '') {
197
+ return String(value);
198
+ }
199
+ return value;
200
+ }
201
+
202
+ const result = {};
203
+ for (const childKey of Object.keys(value)) {
204
+ const childValue = value[childKey];
205
+ if ((childKey === 'filterState' && (childValue === null || childValue === undefined)) ||
206
+ (childKey === 'parentId' && (childValue === '' || childValue === null || childValue === undefined))) {
207
+ continue;
208
+ }
209
+ result[childKey] = canonicalizeAggregateValue(childValue, childKey);
210
+ }
211
+ return result;
212
+ }
213
+
214
+ function assertAggregateDesignReadback(expected, actual) {
215
+ const expectedProjection = canonicalizeAggregateValue(projectAggregateDesignConfig(expected));
216
+ const actualProjection = canonicalizeAggregateValue(projectAggregateDesignConfig(actual));
217
+ if (isDeepStrictEqual(expectedProjection, actualProjection)) {
218
+ return actualProjection;
219
+ }
220
+
221
+ const error = new Error('AGGREGATE_DESIGN_READBACK_MISMATCH');
222
+ error.name = 'AggregateDesignReadbackError';
223
+ error.code = 'AGGREGATE_DESIGN_READBACK_MISMATCH';
224
+ throw error;
225
+ }
226
+
227
+ module.exports = {
228
+ DEFAULT_LIMITS,
229
+ DESIGN_KEYS,
230
+ assertAggregateDesignConfig,
231
+ assertAggregateDesignReadback,
232
+ projectAggregateDesignConfig,
233
+ validateAggregateDesignConfig,
234
+ };
@@ -2,6 +2,7 @@
2
2
 
3
3
  const { createAuthRef, createYidaClient, isAuthRefReady } = require('../core/yida-client');
4
4
  const { CliError } = require('../core/cli-error');
5
+ const { t } = require('../core/i18n');
5
6
  const { searchUsers } = require('../corp-manager/api');
6
7
 
7
8
  const ROLE_CONFIGS = {
@@ -140,6 +141,12 @@ function unique(values) {
140
141
  return [...new Set((values || []).map(value => String(value).trim()).filter(Boolean))];
141
142
  }
142
143
 
144
+ function sameUserIds(left, right) {
145
+ const leftIds = unique(left).sort();
146
+ const rightIds = unique(right).sort();
147
+ return JSON.stringify(leftIds) === JSON.stringify(rightIds);
148
+ }
149
+
143
150
  function toPositiveInt(value, defaultValue) {
144
151
  const parsed = Number.parseInt(value || `${defaultValue}`, 10);
145
152
  if (!Number.isFinite(parsed) || parsed <= 0) {
@@ -310,11 +317,7 @@ async function updateRoleManagers(options = {}, authRef = getAuthRef()) {
310
317
  const action = options.action || 'set';
311
318
  const inputUserIds = unique(options.userIds || options.users || []);
312
319
 
313
- if (action === 'set') {
314
- return saveRoleManagers({ appType, roleType, userIds: inputUserIds }, authRef);
315
- }
316
-
317
- if (inputUserIds.length === 0) {
320
+ if (action !== 'set' && inputUserIds.length === 0) {
318
321
  throw new Error(`${action} 操作必须提供 --users`);
319
322
  }
320
323
 
@@ -323,7 +326,9 @@ async function updateRoleManagers(options = {}, authRef = getAuthRef()) {
323
326
  const previousUserIds = current.roles[roleKey].userIds;
324
327
  let nextUserIds;
325
328
 
326
- if (action === 'add') {
329
+ if (action === 'set') {
330
+ nextUserIds = inputUserIds;
331
+ } else if (action === 'add') {
327
332
  nextUserIds = unique(previousUserIds.concat(inputUserIds));
328
333
  } else if (action === 'remove') {
329
334
  const removeSet = new Set(inputUserIds);
@@ -387,9 +392,22 @@ async function runUpdate(action, positionals, options) {
387
392
  });
388
393
  const current = await getAppPermission(appType);
389
394
  const roleKey = ROLE_CONFIGS[saved.roleType].key;
395
+ if (!sameUserIds(saved.userIds, current.roles[roleKey].userIds)) {
396
+ throw new CliError(t('app_permission.verify_failed'), {
397
+ code: 'APP_PERMISSION_VERIFY_FAILED',
398
+ details: {
399
+ expected: saved.userIds,
400
+ actual: current.roles[roleKey].userIds,
401
+ },
402
+ });
403
+ }
390
404
 
391
405
  printJson({
392
406
  ...saved,
407
+ before: {
408
+ userIds: saved.previousUserIds,
409
+ },
410
+ after: current.roles[roleKey],
393
411
  currentRole: current.roles[roleKey],
394
412
  });
395
413
  }
@@ -420,6 +438,7 @@ module.exports = {
420
438
  USAGE,
421
439
  parseCliOptions,
422
440
  splitList,
441
+ sameUserIds,
423
442
  normalizeRole,
424
443
  normalizeText,
425
444
  normalizeMember,