openyida 2026.8.19 → 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/bin/yida.js +3 -7
- 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/auth/org.js +8 -8
- package/lib/auth/profile.js +6 -6
- package/lib/auth/token-auth.js +39 -28
- package/lib/auth/token-store.js +58 -34
- 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/agent-capabilities.js +14 -102
- package/lib/core/command-manifest.js +1 -1
- package/lib/core/locales/en.js +57 -3
- package/lib/core/locales/zh.js +57 -3
- package/lib/core/query-data.js +21 -0
- package/lib/core/utils.js +15 -11
- 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/matrix-service.js +56 -0
- package/lib/permission/save-permission.js +470 -68
- 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-data-management/SKILL.md +1 -0
- package/yida-skills/skills/yida-form-permission/SKILL.md +108 -77
- 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 |
|
package/bin/yida.js
CHANGED
|
@@ -670,15 +670,13 @@ async function main() {
|
|
|
670
670
|
|
|
671
671
|
case 'login': {
|
|
672
672
|
const loginArgs = applyLoginEnvironmentFlags(args, { inferTargetUrl: true });
|
|
673
|
-
const { getAuthStatus
|
|
673
|
+
const { getAuthStatus } = require('../lib/core/utils');
|
|
674
674
|
if (hasHelpFlag(loginArgs)) {
|
|
675
675
|
printLoginHelp();
|
|
676
676
|
} else {
|
|
677
677
|
assertNoUnsupportedLegacyLoginFlags(rawArgs, loginArgs);
|
|
678
678
|
if (loginArgs.includes('--check-only')) {
|
|
679
679
|
console.log(JSON.stringify(getAuthStatus(buildTokenLoginOptions(loginArgs)), null, 2));
|
|
680
|
-
} else if (isEnvAuthMode()) {
|
|
681
|
-
printLoginResult(getAuthStatus(buildTokenLoginOptions(loginArgs)));
|
|
682
680
|
} else {
|
|
683
681
|
const { tokenLogin } = require('../lib/auth/token-auth');
|
|
684
682
|
const result = await tokenLogin(buildTokenLoginOptions(loginArgs));
|
|
@@ -697,7 +695,7 @@ async function main() {
|
|
|
697
695
|
case 'auth': {
|
|
698
696
|
const subCommand = args[0];
|
|
699
697
|
const authArgs = applyLoginEnvironmentFlags(args.slice(1), { inferTargetUrl: true });
|
|
700
|
-
const { getAuthStatus
|
|
698
|
+
const { getAuthStatus } = require('../lib/core/utils');
|
|
701
699
|
const { tokenLogin, tokenLogout, tokenRefresh } = require('../lib/auth/token-auth');
|
|
702
700
|
if (!subCommand || subCommand === '--help' || subCommand === '-h') {
|
|
703
701
|
printAuthHelp();
|
|
@@ -720,9 +718,7 @@ async function main() {
|
|
|
720
718
|
printLoginHelp();
|
|
721
719
|
} else {
|
|
722
720
|
assertNoUnsupportedLegacyLoginFlags(rawArgs.slice(1), authArgs);
|
|
723
|
-
const result =
|
|
724
|
-
? getAuthStatus(buildTokenLoginOptions(authArgs))
|
|
725
|
-
: await tokenLogin(buildTokenLoginOptions(authArgs));
|
|
721
|
+
const result = await tokenLogin(buildTokenLoginOptions(authArgs));
|
|
726
722
|
printLoginResult(result);
|
|
727
723
|
}
|
|
728
724
|
} else if (subCommand === 'refresh') {
|
|
@@ -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,
|
package/lib/auth/org.js
CHANGED
|
@@ -13,7 +13,7 @@ const {
|
|
|
13
13
|
loadTokenSession,
|
|
14
14
|
normalizeCorpName,
|
|
15
15
|
resolveTokenSession,
|
|
16
|
-
|
|
16
|
+
isEnvTokenAuthMode,
|
|
17
17
|
saveAuthProfilePointer,
|
|
18
18
|
saveProjectLegacyTokenSession,
|
|
19
19
|
saveTokenSession,
|
|
@@ -70,15 +70,15 @@ function createProfileRequiredError(targetCorpId, resolution = {}) {
|
|
|
70
70
|
return error;
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
-
function
|
|
73
|
+
function createEnvTokenSwitchError(targetCorpId, currentSession, resolution = {}) {
|
|
74
74
|
const actualCorpId = currentSession && currentSession.corp_id;
|
|
75
|
-
const error = new Error(
|
|
76
|
-
error.code = '
|
|
77
|
-
error.status = '
|
|
75
|
+
const error = new Error(`当前为 env token 登录态,不能通过 OAuth 切换组织:target=${targetCorpId}, actual=${actualCorpId || 'unknown'}`);
|
|
76
|
+
error.code = 'ORG_SWITCH_ENV_TOKEN_MISMATCH';
|
|
77
|
+
error.status = 'env_token_mismatch';
|
|
78
78
|
error.targetCorpId = targetCorpId;
|
|
79
79
|
error.actualCorpId = actualCorpId;
|
|
80
80
|
error.auth_source = resolution.auth_source || (currentSession && currentSession.auth_source) || 'env';
|
|
81
|
-
error.auth_store = resolution.auth_store || (currentSession && currentSession.auth_store) || '
|
|
81
|
+
error.auth_store = resolution.auth_store || (currentSession && currentSession.auth_store) || 'env';
|
|
82
82
|
error.can_auto_use = false;
|
|
83
83
|
return error;
|
|
84
84
|
}
|
|
@@ -220,8 +220,8 @@ async function switchOrganization(targetCorpId, options = {}) {
|
|
|
220
220
|
if (targetResolution.status === 'profile_required') {
|
|
221
221
|
throw createProfileRequiredError(targetCorpId, targetResolution);
|
|
222
222
|
}
|
|
223
|
-
if (
|
|
224
|
-
throw
|
|
223
|
+
if (isEnvTokenAuthMode(projectOptions.env)) {
|
|
224
|
+
throw createEnvTokenSwitchError(targetCorpId, currentSession, targetResolution);
|
|
225
225
|
}
|
|
226
226
|
|
|
227
227
|
const loginOptions = {
|
package/lib/auth/profile.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const { CliError } = require('../core/cli-error');
|
|
4
4
|
const {
|
|
5
|
-
|
|
5
|
+
isEnvTokenAuthMode,
|
|
6
6
|
listUserAuthProfiles,
|
|
7
7
|
loadAuthProfilePointer,
|
|
8
8
|
loadTokenSession,
|
|
@@ -129,14 +129,14 @@ function resolveSwitchTarget(target, options = {}) {
|
|
|
129
129
|
}
|
|
130
130
|
|
|
131
131
|
function switchAuthProfile(target, options = {}) {
|
|
132
|
-
if (
|
|
132
|
+
if (isEnvTokenAuthMode(options.env || process.env)) {
|
|
133
133
|
throw new CliError(
|
|
134
|
-
'
|
|
134
|
+
'env token mode cannot switch local auth profiles',
|
|
135
135
|
{
|
|
136
|
-
code: '
|
|
136
|
+
code: 'AUTH_PROFILE_SWITCH_ENV_TOKEN',
|
|
137
137
|
details: {
|
|
138
|
-
nextStep: 'Ask the
|
|
139
|
-
next_step: 'Ask the
|
|
138
|
+
nextStep: 'Ask the runtime to inject the target organization token instead of switching local profiles.',
|
|
139
|
+
next_step: 'Ask the runtime to inject the target organization token instead of switching local profiles.',
|
|
140
140
|
},
|
|
141
141
|
}
|
|
142
142
|
);
|
package/lib/auth/token-auth.js
CHANGED
|
@@ -15,14 +15,13 @@ const {
|
|
|
15
15
|
clearAllUserAuthProfiles,
|
|
16
16
|
clearTokenSession,
|
|
17
17
|
deleteUserAuthProfile,
|
|
18
|
-
|
|
18
|
+
isEnvTokenAuthMode,
|
|
19
19
|
listUserAuthProfiles,
|
|
20
20
|
loadLocalTokenSession,
|
|
21
21
|
loadTokenSession,
|
|
22
22
|
loadUserProfileFile,
|
|
23
23
|
maskToken,
|
|
24
24
|
normalizeCorpName,
|
|
25
|
-
normalizeTokenSession,
|
|
26
25
|
resolveTokenSession,
|
|
27
26
|
saveBusinessContext,
|
|
28
27
|
saveTokenSession,
|
|
@@ -274,6 +273,32 @@ function isRefreshAuthRequired(value) {
|
|
|
274
273
|
}
|
|
275
274
|
|
|
276
275
|
async function tokenLogin(options = {}) {
|
|
276
|
+
const env = options.env || process.env;
|
|
277
|
+
if (isEnvTokenAuthMode(env)) {
|
|
278
|
+
const status = tokenStatus(options);
|
|
279
|
+
if (status && status.can_auto_use) {
|
|
280
|
+
return {
|
|
281
|
+
...status,
|
|
282
|
+
ok: true,
|
|
283
|
+
status: 'ok',
|
|
284
|
+
can_auto_use: true,
|
|
285
|
+
message: 'env token bootstrap credentials are already available; OAuth login was skipped',
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
return {
|
|
289
|
+
...status,
|
|
290
|
+
ok: false,
|
|
291
|
+
auth_mode: 'token',
|
|
292
|
+
auth_source: status && status.auth_source ? status.auth_source : 'env',
|
|
293
|
+
auth_store: status && status.auth_store ? status.auth_store : 'env',
|
|
294
|
+
status: status && status.status ? status.status : 'not_logged_in',
|
|
295
|
+
can_auto_use: false,
|
|
296
|
+
message: status && status.message
|
|
297
|
+
? status.message
|
|
298
|
+
: 'env token bootstrap credential is missing. Ask the runtime to inject OPENYIDA_ACCESS_TOKEN or OPENYIDA_REFRESH_TOKEN.',
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
|
|
277
302
|
const baseUrl = resolveTokenBaseUrl(options);
|
|
278
303
|
const authBaseUrl = appendPath(baseUrl, DEFAULT_AUTH_PATH_PREFIX);
|
|
279
304
|
const clientId = options.clientId || process.env.OPENYIDA_DINGTALK_CLIENT_ID || DINGTALK_OAUTH_CLIENT_ID;
|
|
@@ -315,21 +340,21 @@ function tokenStatus(options = {}) {
|
|
|
315
340
|
const session = resolution.session;
|
|
316
341
|
if (!session || (!session.access_token && !session.refresh_token)) {
|
|
317
342
|
const env = options.env || process.env;
|
|
318
|
-
const
|
|
343
|
+
const envTokenMode = isEnvTokenAuthMode(env);
|
|
319
344
|
const status = {
|
|
320
345
|
ok: false,
|
|
321
346
|
auth_mode: 'token',
|
|
322
|
-
auth_source: resolution.auth_source || (
|
|
347
|
+
auth_source: resolution.auth_source || (envTokenMode ? 'env' : undefined),
|
|
323
348
|
auth_store: resolution.auth_store,
|
|
324
349
|
auth_profile: resolution.auth_profile,
|
|
325
350
|
status: resolution.status === 'profile_required' ? 'profile_required' : 'not_logged_in',
|
|
326
351
|
can_auto_use: false,
|
|
327
|
-
failure_reason:
|
|
328
|
-
message: resolution.message || (
|
|
329
|
-
? '
|
|
352
|
+
failure_reason: envTokenMode ? 'env_token_missing' : resolution.status,
|
|
353
|
+
message: resolution.message || (envTokenMode
|
|
354
|
+
? 'env token bootstrap credential is missing. Ask the runtime to inject OPENYIDA_ACCESS_TOKEN or OPENYIDA_REFRESH_TOKEN.'
|
|
330
355
|
: undefined),
|
|
331
|
-
next_step: resolution.next_step || (
|
|
332
|
-
? 'Ask the
|
|
356
|
+
next_step: resolution.next_step || (envTokenMode
|
|
357
|
+
? 'Ask the runtime to inject OPENYIDA_ACCESS_TOKEN or OPENYIDA_REFRESH_TOKEN; stop until credentials are available.'
|
|
333
358
|
: 'Run openyida login to add an auth profile.'),
|
|
334
359
|
next_step_commands: resolution.next_step_commands,
|
|
335
360
|
candidate_count: resolution.candidate_count,
|
|
@@ -338,7 +363,7 @@ function tokenStatus(options = {}) {
|
|
|
338
363
|
persistence_scope: resolution.persistence_scope,
|
|
339
364
|
warning: resolution.warning,
|
|
340
365
|
};
|
|
341
|
-
if (!
|
|
366
|
+
if (!envTokenMode) {
|
|
342
367
|
status.token_file = require('./token-store').getTokenFilePath(options);
|
|
343
368
|
}
|
|
344
369
|
return status;
|
|
@@ -353,7 +378,7 @@ function tokenStatus(options = {}) {
|
|
|
353
378
|
persistence_scope: session.persistence_scope,
|
|
354
379
|
user_auth_store_writable: session.user_auth_store_writable,
|
|
355
380
|
warning: session.warning,
|
|
356
|
-
status: '
|
|
381
|
+
status: 'ok',
|
|
357
382
|
can_auto_use: true,
|
|
358
383
|
refresh_token: maskToken(session.refresh_token),
|
|
359
384
|
base_url: session.base_url,
|
|
@@ -446,7 +471,7 @@ async function tokenRefresh(options = {}) {
|
|
|
446
471
|
}
|
|
447
472
|
}
|
|
448
473
|
const env = options.env || process.env;
|
|
449
|
-
if (
|
|
474
|
+
if (isEnvTokenAuthMode(env) && ['env', 'project_legacy'].includes(session.auth_source)) {
|
|
450
475
|
env.OPENYIDA_ACCESS_TOKEN = normalized.access_token;
|
|
451
476
|
if (normalized.refresh_token) {
|
|
452
477
|
env.OPENYIDA_REFRESH_TOKEN = normalized.refresh_token;
|
|
@@ -454,10 +479,10 @@ async function tokenRefresh(options = {}) {
|
|
|
454
479
|
if (normalized.expires_at) {
|
|
455
480
|
env.OPENYIDA_ACCESS_TOKEN_EXPIRES_AT = String(normalized.expires_at);
|
|
456
481
|
}
|
|
457
|
-
return
|
|
482
|
+
return saveTokenSession({
|
|
458
483
|
...normalized,
|
|
459
484
|
auth_source: 'env',
|
|
460
|
-
});
|
|
485
|
+
}, options);
|
|
461
486
|
}
|
|
462
487
|
return saveTokenSession(normalized, options);
|
|
463
488
|
}
|
|
@@ -496,20 +521,6 @@ function toLogoutProfileSummary(session = {}) {
|
|
|
496
521
|
}
|
|
497
522
|
|
|
498
523
|
async function tokenLogout(options = {}) {
|
|
499
|
-
const env = options.env || process.env;
|
|
500
|
-
if (isHostInjectedTokenMode(env)) {
|
|
501
|
-
return {
|
|
502
|
-
ok: true,
|
|
503
|
-
auth_mode: 'token',
|
|
504
|
-
status: 'host_injected_noop',
|
|
505
|
-
can_auto_use: false,
|
|
506
|
-
auth_source: 'env',
|
|
507
|
-
auth_store: 'host_injected',
|
|
508
|
-
persistence_scope: 'host',
|
|
509
|
-
message: 'host-injected token mode does not write or delete local auth profiles',
|
|
510
|
-
};
|
|
511
|
-
}
|
|
512
|
-
|
|
513
524
|
const authProfile = options.authProfile || options.profile;
|
|
514
525
|
const deleteAllProfiles = options.allProfiles === true || options.all === true;
|
|
515
526
|
if (deleteAllProfiles) {
|