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.
- package/README.md +1 -1
- package/lib/aggregate-table/aggregate-table.js +37 -14
- package/lib/aggregate-table/contract.js +234 -0
- package/lib/app-permission/app-permission.js +25 -6
- package/lib/connector/api.js +144 -36
- package/lib/connector/connector-create.js +4 -4
- package/lib/connector/connector-delete.js +7 -10
- package/lib/connector/connector-test.js +3 -2
- package/lib/connector/contract.js +98 -0
- package/lib/core/command-manifest.js +1 -1
- package/lib/core/locales/en.js +56 -2
- package/lib/core/locales/zh.js +56 -2
- package/lib/core/yida-client.js +19 -0
- package/lib/corp-manager/api.js +15 -3
- package/lib/corp-manager/corp-manager.js +34 -4
- package/lib/integration/connector-presets.js +2 -3
- package/lib/integration/integration-create.js +161 -80
- package/lib/integration/integration-process-builder.js +177 -48
- package/lib/integration/integration-spec-builder.js +171 -48
- package/lib/integration/integration-view-builder.js +26 -25
- package/lib/page-config/save-share-config.js +111 -10
- package/lib/permission/get-permission.js +15 -0
- package/lib/permission/save-permission.js +361 -79
- package/lib/process/configure-process.js +114 -71
- package/lib/process/create-process.js +4 -1
- package/lib/process/services/process-compiler.js +53 -28
- package/lib/report/append.js +18 -1
- package/lib/report/contract.js +158 -0
- package/lib/report/http.js +28 -5
- package/lib/report/index.js +23 -1
- package/package.json +5 -1
- package/scripts/i18n-baseline.json +45 -23
- package/scripts/validate-i18n.js +45 -15
- package/yida-skills/skills/yida-app-permission/SKILL.md +39 -47
- package/yida-skills/skills/yida-connector/SKILL.md +6 -6
- package/yida-skills/skills/yida-corp-manager/SKILL.md +45 -34
- package/yida-skills/skills/yida-form-permission/SKILL.md +108 -139
- package/yida-skills/skills/yida-integration/references/integration-node-schemas.md +2 -2
- 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
|
|
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 }).
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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 === '
|
|
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,
|