openyida 2026.5.17 → 2026.5.19-beta.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 +12 -3
- package/bin/yida.js +6 -0
- package/lib/app-permission/app-permission.js +459 -0
- package/lib/core/command-manifest.js +5 -1
- package/lib/core/env-manager.js +36 -2
- package/lib/core/locales/ar.js +1 -0
- package/lib/core/locales/de.js +1 -0
- package/lib/core/locales/en.js +1 -0
- package/lib/core/locales/es.js +1 -0
- package/lib/core/locales/fr.js +1 -0
- package/lib/core/locales/hi.js +1 -0
- package/lib/core/locales/ja.js +1 -0
- package/lib/core/locales/ko.js +1 -0
- package/lib/core/locales/pt.js +1 -0
- package/lib/core/locales/vi.js +1 -0
- package/lib/core/locales/zh-HK.js +1 -0
- package/lib/core/locales/zh.js +1 -0
- package/lib/core/utils.js +14 -0
- package/lib/integration/integration-create.js +63 -0
- package/lib/integration/integration-process-builder.js +89 -4
- package/lib/integration/integration-view-builder.js +48 -19
- package/package.json +1 -1
- package/scripts/e2e-real/skill-coverage.js +2 -0
- package/yida-skills/SKILL.md +2 -0
- package/yida-skills/references/formula-functions.md +11 -4
- package/yida-skills/skills/yida-app-permission/SKILL.md +89 -0
- package/yida-skills/skills/yida-business-rule/SKILL.md +135 -0
- package/yida-skills/skills/yida-formula/SKILL.md +18 -15
- package/yida-skills/skills/yida-integration/SKILL.md +5 -1
- package/yida-skills/skills/yida-login/SKILL.md +32 -0
package/README.md
CHANGED
|
@@ -242,6 +242,8 @@ Use `npm run test:e2e:real:cleanup` to list recorded disposable resources. OpenY
|
|
|
242
242
|
openyida connector smart-create --curl "curl https://api.example.com/users"
|
|
243
243
|
openyida connector list
|
|
244
244
|
openyida integration create APP_XXX FORM_XXX "Sync customer data"
|
|
245
|
+
openyida integration create APP_XXX FORM_XXX "Approval result notify" \
|
|
246
|
+
--events processFinish --approval-actions agree,disagree --receivers 123456
|
|
245
247
|
openyida create-report APP_XXX "Sales Dashboard" .cache/openyida/reports/charts.json
|
|
246
248
|
openyida append-chart APP_XXX REPORT_XXX .cache/openyida/reports/chart.json
|
|
247
249
|
```
|
|
@@ -258,13 +260,13 @@ Run `openyida --help` or `openyida <command> --help` for detailed usage.
|
|
|
258
260
|
| `openyida env setup` | Choose a customer-friendly login environment preset: public, overseas, Alibaba intranet, or private deployment |
|
|
259
261
|
| `openyida env <list\|show\|switch\|add\|remove>` | Manage public/private Yida environment profiles |
|
|
260
262
|
| `openyida commands [--json]` | Emit the machine-readable command manifest |
|
|
261
|
-
| `openyida login [--qr\|--agent-qr\|--codex\|--browser] [--env <name>\|--overseas\|--yidaapps] [--corp-id <corpId>]` | Log in to Yida |
|
|
263
|
+
| `openyida login [--qr\|--agent-qr\|--codex\|--browser] [--env <name>\|--intl\|--overseas\|--global\|--yidaapps] [--corp-id <corpId>]` | Log in to Yida |
|
|
262
264
|
| `openyida logout` | Log out or switch account |
|
|
263
265
|
| `openyida auth <status\|login\|refresh\|logout>` | Manage login status |
|
|
264
266
|
| `openyida org list` | List accessible organizations |
|
|
265
267
|
| `openyida org switch --corp-id <corpId>` | Switch organization without logging in again |
|
|
266
268
|
|
|
267
|
-
Environment selectors such as `--env intl`, `--overseas`, and `--yidaapps` can be used on login-required commands to choose the target Yida environment for that run.
|
|
269
|
+
Environment selectors such as `--env intl`, `--intl`, `--overseas`, `--global`, and `--yidaapps` can be used on login-required commands to choose the target Yida environment for that run. The `intl` preset targets Global YiDA at `https://www.yidaapps.com` and uses DingTalk International OAuth at `https://login.dingtalk.io`; use `openyida login --browser --intl` when you need cookies accepted by Global YiDA business APIs.
|
|
268
270
|
|
|
269
271
|
### Applications
|
|
270
272
|
|
|
@@ -274,6 +276,7 @@ Environment selectors such as `--env intl`, `--overseas`, and `--yidaapps` can b
|
|
|
274
276
|
| `openyida corp-efficiency [overview\|details\|detail\|groups\|notify] [options] [--open\|--no-open]` | Query enterprise efficiency metrics, detail report entries, and related notification actions |
|
|
275
277
|
| `openyida create-app "<name>"\|--name <name> [options] [--open\|--no-open]` | Create an application and output `appType` |
|
|
276
278
|
| `openyida update-app <appType> --name "..."` | Update application metadata |
|
|
279
|
+
| `openyida app-permission <get\|set\|add\|remove\|search-user> ...` | Manage app primary admins, data admins, and developer members |
|
|
277
280
|
| `openyida export <appType> [output]` | Export an application migration package |
|
|
278
281
|
| `openyida import <file> [name]` | Import a migration package into a target environment |
|
|
279
282
|
|
|
@@ -319,13 +322,15 @@ Environment selectors such as `--env intl`, `--overseas`, and `--yidaapps` can b
|
|
|
319
322
|
| `openyida create-report <appType> "<name>" <charts.json> [--open\|--no-open]` | Create a Yida report |
|
|
320
323
|
| `openyida append-chart <appType> <reportId> <charts.json> [--open\|--no-open]` | Append a chart to an existing report |
|
|
321
324
|
| `openyida connector <sub-command>` | Manage HTTP connectors, actions, tests, and auth accounts |
|
|
322
|
-
| `openyida integration create <appType> <formUuid> <flowName> [options]` | Create an integration automation flow |
|
|
325
|
+
| `openyida integration create <appType> <formUuid> <flowName> [options]` | Create an integration automation flow, including form and approval-process events |
|
|
323
326
|
| `openyida integration list <appType> [--form-uuid <uuid>] [--status y\|n] [--json]` | List automation flows in an app, optionally filtered by form/status |
|
|
324
327
|
| `openyida integration enable <appType> <formUuid> <processCode>` | Enable an automation flow |
|
|
325
328
|
| `openyida integration disable <appType> <formUuid> <processCode>` | Disable an automation flow |
|
|
326
329
|
| `openyida dws <command> [args]` | Access DingTalk CLI capabilities such as contacts, calendar, todo, and approval |
|
|
327
330
|
| `openyida dingtalk-link <url> [--target fullScreen] [--legacy-scheme] [--json]` | Generate DingTalk AppLink URLs for opening pages in DingTalk; use `--legacy-scheme` only when old `dingtalk://` links are required |
|
|
328
331
|
|
|
332
|
+
`openyida integration create` supports form events (`insert`, `update`, `delete`, `comment`) and approval events (`processFinish`, `activityTask`; aliases: `approval`, `approvalNode`). Approval events require `--approval-actions agree,disagree,terminated`; `activityTask` also requires `--approval-node-ids <nodeId,...>`.
|
|
333
|
+
|
|
329
334
|
### Utilities
|
|
330
335
|
|
|
331
336
|
| Command | Description |
|
|
@@ -441,11 +446,15 @@ Scan the QR code to join the OpenYida DingTalk user group for updates and suppor
|
|
|
441
446
|
|
|
442
447
|
Thanks to everyone who has contributed to OpenYida. Read the [Contributing Guide](./CONTRIBUTING.md) to get involved.
|
|
443
448
|
|
|
449
|
+
Latest contributors: [DDlixin1](https://github.com/DDlixin1), [fcloud](https://github.com/fcloud).
|
|
450
|
+
|
|
444
451
|
<!-- openyida-contributors:start -->
|
|
445
452
|
|
|
446
453
|
<p>
|
|
447
454
|
<a href="https://github.com/yize"><img src="https://github.com/yize.png?size=48" width="48" height="48" alt="九神" title="九神" /></a>
|
|
448
455
|
<a href="https://github.com/alex-mm"><img src="https://github.com/alex-mm.png?size=48" width="48" height="48" alt="天晟" title="天晟" /></a>
|
|
456
|
+
<a href="https://github.com/DDlixin1"><img src="https://github.com/DDlixin1.png?size=48" width="48" height="48" alt="DDlixin1" title="DDlixin1" /></a>
|
|
457
|
+
<a href="https://github.com/fcloud"><img src="https://github.com/fcloud.png?size=48" width="48" height="48" alt="Aiden Wu (fcloud)" title="Aiden Wu (fcloud)" /></a>
|
|
449
458
|
<a href="https://github.com/nicky1108"><img src="https://github.com/nicky1108.png?size=48" width="48" height="48" alt="nicky1108" title="nicky1108" /></a>
|
|
450
459
|
<a href="https://github.com/angelinheys"><img src="https://github.com/angelinheys.png?size=48" width="48" height="48" alt="angelinheys" title="angelinheys" /></a>
|
|
451
460
|
<a href="https://github.com/yipengmu"><img src="https://github.com/yipengmu.png?size=48" width="48" height="48" alt="yipengmu" title="yipengmu" /></a>
|
package/bin/yida.js
CHANGED
|
@@ -692,6 +692,12 @@ async function main() {
|
|
|
692
692
|
break;
|
|
693
693
|
}
|
|
694
694
|
|
|
695
|
+
case 'app-permission': {
|
|
696
|
+
const { run: runAppPermission } = require('../lib/app-permission/app-permission');
|
|
697
|
+
await runAppPermission(args);
|
|
698
|
+
break;
|
|
699
|
+
}
|
|
700
|
+
|
|
695
701
|
case 'data': {
|
|
696
702
|
if (args.length < 2) {
|
|
697
703
|
warn('用法: openyida data <action> <resource> [args] [options]');
|
|
@@ -0,0 +1,459 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const querystring = require('querystring');
|
|
4
|
+
|
|
5
|
+
const {
|
|
6
|
+
loadCookieData,
|
|
7
|
+
triggerLogin,
|
|
8
|
+
resolveBaseUrl,
|
|
9
|
+
httpGet,
|
|
10
|
+
httpPost,
|
|
11
|
+
requestWithAutoLogin,
|
|
12
|
+
} = require('../core/utils');
|
|
13
|
+
const { searchUsers } = require('../corp-manager/api');
|
|
14
|
+
|
|
15
|
+
const ROLE_CONFIGS = {
|
|
16
|
+
MAIN: {
|
|
17
|
+
key: 'main',
|
|
18
|
+
label: '应用主管理员',
|
|
19
|
+
memberField: 'managers',
|
|
20
|
+
idField: 'managerIdList',
|
|
21
|
+
required: true,
|
|
22
|
+
},
|
|
23
|
+
DATA: {
|
|
24
|
+
key: 'data',
|
|
25
|
+
label: '数据管理员',
|
|
26
|
+
memberField: 'dataManagers',
|
|
27
|
+
idField: 'dataManagerUserIdList',
|
|
28
|
+
required: false,
|
|
29
|
+
},
|
|
30
|
+
DEV: {
|
|
31
|
+
key: 'dev',
|
|
32
|
+
label: '开发成员',
|
|
33
|
+
memberField: 'devManagers',
|
|
34
|
+
idField: 'devManagerUserIdList',
|
|
35
|
+
required: false,
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const ROLE_ALIASES = {
|
|
40
|
+
main: 'MAIN',
|
|
41
|
+
primary: 'MAIN',
|
|
42
|
+
owner: 'MAIN',
|
|
43
|
+
admin: 'MAIN',
|
|
44
|
+
manager: 'MAIN',
|
|
45
|
+
mainManagers: 'MAIN',
|
|
46
|
+
MAIN: 'MAIN',
|
|
47
|
+
|
|
48
|
+
data: 'DATA',
|
|
49
|
+
dataAdmin: 'DATA',
|
|
50
|
+
dataManager: 'DATA',
|
|
51
|
+
dataManagers: 'DATA',
|
|
52
|
+
DATA: 'DATA',
|
|
53
|
+
|
|
54
|
+
dev: 'DEV',
|
|
55
|
+
develop: 'DEV',
|
|
56
|
+
developer: 'DEV',
|
|
57
|
+
development: 'DEV',
|
|
58
|
+
devManager: 'DEV',
|
|
59
|
+
devManagers: 'DEV',
|
|
60
|
+
DEV: 'DEV',
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const USAGE = `openyida app-permission - 应用级管理员设置
|
|
64
|
+
|
|
65
|
+
Usage:
|
|
66
|
+
openyida app-permission search-user <keyword> [--dept <text>] [--size N]
|
|
67
|
+
openyida app-permission get <appType>
|
|
68
|
+
openyida app-permission set <appType> <main|data|dev> --users <userId1,userId2>
|
|
69
|
+
openyida app-permission set <appType> <data|dev> --clear
|
|
70
|
+
openyida app-permission add <appType> <main|data|dev> --users <userId1,userId2>
|
|
71
|
+
openyida app-permission remove <appType> <main|data|dev> --users <userId1,userId2>
|
|
72
|
+
|
|
73
|
+
Examples:
|
|
74
|
+
openyida app-permission get APP_XXX
|
|
75
|
+
openyida app-permission add APP_XXX data --users manager7350
|
|
76
|
+
openyida app-permission set APP_XXX dev --users user001,user002
|
|
77
|
+
`;
|
|
78
|
+
|
|
79
|
+
function fail(message) {
|
|
80
|
+
console.error(message);
|
|
81
|
+
console.error(USAGE);
|
|
82
|
+
process.exit(1);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function printJson(payload) {
|
|
86
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function getAuthRef() {
|
|
90
|
+
let cookieData = loadCookieData();
|
|
91
|
+
if (!cookieData || !cookieData.cookies || !cookieData.csrf_token) {
|
|
92
|
+
cookieData = triggerLogin();
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (!cookieData || !cookieData.cookies || !cookieData.csrf_token) {
|
|
96
|
+
throw new Error('无法获取有效登录态或 CSRF Token');
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return {
|
|
100
|
+
csrfToken: cookieData.csrf_token,
|
|
101
|
+
cookies: cookieData.cookies,
|
|
102
|
+
baseUrl: resolveBaseUrl(cookieData),
|
|
103
|
+
cookieData,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function assertSuccess(result, action) {
|
|
108
|
+
if (result && result.success) {
|
|
109
|
+
return result;
|
|
110
|
+
}
|
|
111
|
+
const message = result && (result.errorMsg || result.message || result.errorCode);
|
|
112
|
+
throw new Error(`${action}失败${message ? `:${message}` : ''}`);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function normalizeRole(role) {
|
|
116
|
+
const normalized = ROLE_ALIASES[String(role || '').trim()];
|
|
117
|
+
if (!normalized) {
|
|
118
|
+
throw new Error(`无效角色:${role},可用值:main, data, dev`);
|
|
119
|
+
}
|
|
120
|
+
return normalized;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function normalizeText(value) {
|
|
124
|
+
if (value === null || value === undefined) {
|
|
125
|
+
return '';
|
|
126
|
+
}
|
|
127
|
+
if (typeof value === 'string') {
|
|
128
|
+
return value;
|
|
129
|
+
}
|
|
130
|
+
if (typeof value === 'object') {
|
|
131
|
+
return value.zh_CN || value.pureEn_US || value.en_US || value.value || value.text || value.label || '';
|
|
132
|
+
}
|
|
133
|
+
return String(value);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function validateAppType(appType) {
|
|
137
|
+
if (!appType) {
|
|
138
|
+
throw new Error('缺少 appType');
|
|
139
|
+
}
|
|
140
|
+
if (/[/?#]/.test(appType)) {
|
|
141
|
+
throw new Error(`无效 appType:${appType}`);
|
|
142
|
+
}
|
|
143
|
+
return appType;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function splitList(value) {
|
|
147
|
+
if (!value || value === true) {
|
|
148
|
+
return [];
|
|
149
|
+
}
|
|
150
|
+
if (Array.isArray(value)) {
|
|
151
|
+
return value;
|
|
152
|
+
}
|
|
153
|
+
return String(value).split(',').map(item => item.trim()).filter(Boolean);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function unique(values) {
|
|
157
|
+
return [...new Set((values || []).map(value => String(value).trim()).filter(Boolean))];
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function toPositiveInt(value, defaultValue) {
|
|
161
|
+
const parsed = Number.parseInt(value || `${defaultValue}`, 10);
|
|
162
|
+
if (!Number.isFinite(parsed) || parsed <= 0) {
|
|
163
|
+
return defaultValue;
|
|
164
|
+
}
|
|
165
|
+
return parsed;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function parseCliOptions(tokens) {
|
|
169
|
+
const positionals = [];
|
|
170
|
+
const options = {};
|
|
171
|
+
|
|
172
|
+
for (let i = 0; i < tokens.length; i += 1) {
|
|
173
|
+
const token = tokens[i];
|
|
174
|
+
if (token.startsWith('--')) {
|
|
175
|
+
const key = token.slice(2).replace(/-/g, '_');
|
|
176
|
+
const next = tokens[i + 1];
|
|
177
|
+
if (next !== undefined && !next.startsWith('--')) {
|
|
178
|
+
options[key] = next;
|
|
179
|
+
i += 1;
|
|
180
|
+
} else {
|
|
181
|
+
options[key] = true;
|
|
182
|
+
}
|
|
183
|
+
} else {
|
|
184
|
+
positionals.push(token);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
return { positionals, options };
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function buildCommonParams(authRef, params = {}) {
|
|
192
|
+
return {
|
|
193
|
+
_csrf_token: authRef.csrfToken,
|
|
194
|
+
_locale_time_zone_offset: '28800000',
|
|
195
|
+
_stamp: String(Date.now()),
|
|
196
|
+
...params,
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
async function appGet(appType, path, params, authRef = getAuthRef()) {
|
|
201
|
+
const safeAppType = validateAppType(appType);
|
|
202
|
+
return requestWithAutoLogin(
|
|
203
|
+
auth => httpGet(
|
|
204
|
+
auth.baseUrl,
|
|
205
|
+
`/${safeAppType}/${path.replace(/^\/+/, '')}`,
|
|
206
|
+
buildCommonParams(auth, params),
|
|
207
|
+
auth.cookies,
|
|
208
|
+
),
|
|
209
|
+
authRef,
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
async function appPost(appType, path, params, authRef = getAuthRef()) {
|
|
214
|
+
const safeAppType = validateAppType(appType);
|
|
215
|
+
return requestWithAutoLogin(
|
|
216
|
+
auth => httpPost(
|
|
217
|
+
auth.baseUrl,
|
|
218
|
+
`/${safeAppType}/${path.replace(/^\/+/, '')}`,
|
|
219
|
+
querystring.stringify(buildCommonParams(auth, params)),
|
|
220
|
+
auth.cookies,
|
|
221
|
+
),
|
|
222
|
+
authRef,
|
|
223
|
+
);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function normalizeMember(record) {
|
|
227
|
+
const userId = record.userId || record.emplId || record.id || record.key || record.workNo || '';
|
|
228
|
+
const userName = normalizeText(
|
|
229
|
+
record.displayName ||
|
|
230
|
+
record.defaultName ||
|
|
231
|
+
record.name ||
|
|
232
|
+
record.nickName ||
|
|
233
|
+
record.label ||
|
|
234
|
+
record.userName,
|
|
235
|
+
);
|
|
236
|
+
|
|
237
|
+
return {
|
|
238
|
+
userId,
|
|
239
|
+
userName,
|
|
240
|
+
dingtalkId: record.dingtalkId || '',
|
|
241
|
+
avatar: record.personalPhoto || record.personalPhotoUrl || record.avatar || '',
|
|
242
|
+
companyNo: record.companyNo || '',
|
|
243
|
+
workStatus: record.workStatus || '',
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function memberIds(members) {
|
|
248
|
+
return unique((members || []).map(member => member.userId || member.emplId || member.id || member.key));
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
function idsFromContent(content, config) {
|
|
252
|
+
const idValue = content[config.idField];
|
|
253
|
+
const idsFromField = typeof idValue === 'string' ? splitList(idValue) : [];
|
|
254
|
+
if (idsFromField.length > 0) {
|
|
255
|
+
return unique(idsFromField);
|
|
256
|
+
}
|
|
257
|
+
return memberIds(content[config.memberField] || []);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function normalizeRolePayload(content, roleType) {
|
|
261
|
+
const config = ROLE_CONFIGS[roleType];
|
|
262
|
+
const members = (content[config.memberField] || []).map(normalizeMember);
|
|
263
|
+
const ids = idsFromContent(content, config);
|
|
264
|
+
|
|
265
|
+
return {
|
|
266
|
+
role: config.key,
|
|
267
|
+
roleType,
|
|
268
|
+
roleLabel: config.label,
|
|
269
|
+
required: config.required,
|
|
270
|
+
userIds: ids,
|
|
271
|
+
members,
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function normalizeAppPermission(content = {}) {
|
|
276
|
+
return {
|
|
277
|
+
success: true,
|
|
278
|
+
appType: content.appType || '',
|
|
279
|
+
appName: normalizeText(content.appName),
|
|
280
|
+
sentryMode: content.sentryMode || '',
|
|
281
|
+
isAccessControl: content.isAccessControl || '',
|
|
282
|
+
allowExternalAddressBook: content.allowExternalAddressBook || '',
|
|
283
|
+
newAllowExternalAddressBook: content.newAllowExternalAddressBook || '',
|
|
284
|
+
currentUserAdminType: content.adminType || '',
|
|
285
|
+
roles: {
|
|
286
|
+
main: normalizeRolePayload(content, 'MAIN'),
|
|
287
|
+
data: normalizeRolePayload(content, 'DATA'),
|
|
288
|
+
dev: normalizeRolePayload(content, 'DEV'),
|
|
289
|
+
},
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
async function getAppPermission(appType, authRef = getAuthRef()) {
|
|
294
|
+
const result = await appGet(
|
|
295
|
+
appType,
|
|
296
|
+
'/query/app/getAppIncludingAecpInfo.json',
|
|
297
|
+
{ appKey: appType },
|
|
298
|
+
authRef,
|
|
299
|
+
);
|
|
300
|
+
assertSuccess(result, '查询应用管理员设置');
|
|
301
|
+
return normalizeAppPermission(result.content || {});
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
async function saveRoleManagers(options = {}, authRef = getAuthRef()) {
|
|
305
|
+
const appType = validateAppType(options.appType);
|
|
306
|
+
const roleType = normalizeRole(options.role || options.roleType);
|
|
307
|
+
const userIds = unique(options.userIds || options.users || []);
|
|
308
|
+
|
|
309
|
+
if (ROLE_CONFIGS[roleType].required && userIds.length === 0) {
|
|
310
|
+
throw new Error(`${ROLE_CONFIGS[roleType].label}不能为空`);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
const result = await appPost(
|
|
314
|
+
appType,
|
|
315
|
+
'/query/app/updateAppAdmin.json',
|
|
316
|
+
{
|
|
317
|
+
adminType: roleType,
|
|
318
|
+
managers: userIds.join(','),
|
|
319
|
+
},
|
|
320
|
+
authRef,
|
|
321
|
+
);
|
|
322
|
+
assertSuccess(result, '保存应用管理员设置');
|
|
323
|
+
|
|
324
|
+
return {
|
|
325
|
+
success: true,
|
|
326
|
+
appType,
|
|
327
|
+
role: ROLE_CONFIGS[roleType].key,
|
|
328
|
+
roleType,
|
|
329
|
+
roleLabel: ROLE_CONFIGS[roleType].label,
|
|
330
|
+
userIds,
|
|
331
|
+
content: result.content,
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
async function updateRoleManagers(options = {}, authRef = getAuthRef()) {
|
|
336
|
+
const appType = validateAppType(options.appType);
|
|
337
|
+
const roleType = normalizeRole(options.role || options.roleType);
|
|
338
|
+
const action = options.action || 'set';
|
|
339
|
+
const inputUserIds = unique(options.userIds || options.users || []);
|
|
340
|
+
|
|
341
|
+
if (action === 'set') {
|
|
342
|
+
return saveRoleManagers({ appType, roleType, userIds: inputUserIds }, authRef);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
if (inputUserIds.length === 0) {
|
|
346
|
+
throw new Error(`${action} 操作必须提供 --users`);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
const current = await getAppPermission(appType, authRef);
|
|
350
|
+
const roleKey = ROLE_CONFIGS[roleType].key;
|
|
351
|
+
const previousUserIds = current.roles[roleKey].userIds;
|
|
352
|
+
let nextUserIds;
|
|
353
|
+
|
|
354
|
+
if (action === 'add') {
|
|
355
|
+
nextUserIds = unique(previousUserIds.concat(inputUserIds));
|
|
356
|
+
} else if (action === 'remove') {
|
|
357
|
+
const removeSet = new Set(inputUserIds);
|
|
358
|
+
nextUserIds = previousUserIds.filter(userId => !removeSet.has(userId));
|
|
359
|
+
} else {
|
|
360
|
+
throw new Error(`未知操作:${action}`);
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
const saved = await saveRoleManagers({ appType, roleType, userIds: nextUserIds }, authRef);
|
|
364
|
+
return {
|
|
365
|
+
...saved,
|
|
366
|
+
action,
|
|
367
|
+
previousUserIds,
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
async function runSearchUser(positionals, options) {
|
|
372
|
+
const keyword = positionals[0];
|
|
373
|
+
if (!keyword) {
|
|
374
|
+
fail('缺少搜索关键词');
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
printJson(await searchUsers({
|
|
378
|
+
keyword,
|
|
379
|
+
dept: options.dept || options.department,
|
|
380
|
+
size: toPositiveInt(options.size, 50),
|
|
381
|
+
}));
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
async function runGet(positionals) {
|
|
385
|
+
const appType = positionals[0];
|
|
386
|
+
if (!appType) {
|
|
387
|
+
fail('缺少 appType');
|
|
388
|
+
}
|
|
389
|
+
printJson(await getAppPermission(appType));
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
async function runUpdate(action, positionals, options) {
|
|
393
|
+
const appType = positionals[0];
|
|
394
|
+
const role = positionals[1];
|
|
395
|
+
if (!appType) {
|
|
396
|
+
fail('缺少 appType');
|
|
397
|
+
}
|
|
398
|
+
if (!role) {
|
|
399
|
+
fail('缺少角色:main、data 或 dev');
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
const userIds = options.clear ? [] : splitList(options.users || options.user || options.user_ids);
|
|
403
|
+
if (!options.clear && userIds.length === 0) {
|
|
404
|
+
fail(`${action} 操作必须提供 --users <userId1,userId2>;清空 data/dev 请使用 --clear`);
|
|
405
|
+
}
|
|
406
|
+
if (options.clear && action !== 'set') {
|
|
407
|
+
fail('--clear 只支持 set 操作');
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
const saved = await updateRoleManagers({
|
|
411
|
+
action,
|
|
412
|
+
appType,
|
|
413
|
+
role,
|
|
414
|
+
userIds,
|
|
415
|
+
});
|
|
416
|
+
const current = await getAppPermission(appType);
|
|
417
|
+
const roleKey = ROLE_CONFIGS[saved.roleType].key;
|
|
418
|
+
|
|
419
|
+
printJson({
|
|
420
|
+
...saved,
|
|
421
|
+
currentRole: current.roles[roleKey],
|
|
422
|
+
});
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
async function run(args) {
|
|
426
|
+
const { positionals, options } = parseCliOptions(args);
|
|
427
|
+
const action = positionals.shift();
|
|
428
|
+
|
|
429
|
+
if (!action || action === '--help' || action === '-h') {
|
|
430
|
+
console.log(USAGE);
|
|
431
|
+
return;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
if (action === 'search-user') {
|
|
435
|
+
await runSearchUser(positionals, options);
|
|
436
|
+
} else if (action === 'get' || action === 'list') {
|
|
437
|
+
await runGet(positionals);
|
|
438
|
+
} else if (['set', 'add', 'remove'].includes(action)) {
|
|
439
|
+
await runUpdate(action, positionals, options);
|
|
440
|
+
} else {
|
|
441
|
+
fail(`未知 app-permission 子命令:${action}`);
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
module.exports = {
|
|
446
|
+
ROLE_CONFIGS,
|
|
447
|
+
ROLE_ALIASES,
|
|
448
|
+
USAGE,
|
|
449
|
+
parseCliOptions,
|
|
450
|
+
splitList,
|
|
451
|
+
normalizeRole,
|
|
452
|
+
normalizeText,
|
|
453
|
+
normalizeMember,
|
|
454
|
+
normalizeAppPermission,
|
|
455
|
+
getAppPermission,
|
|
456
|
+
saveRoleManagers,
|
|
457
|
+
updateRoleManagers,
|
|
458
|
+
run,
|
|
459
|
+
};
|
|
@@ -20,7 +20,7 @@ const COMMAND_GROUPS = [
|
|
|
20
20
|
id: 'auth',
|
|
21
21
|
titleKey: 'help.group_auth',
|
|
22
22
|
commands: [
|
|
23
|
-
command('login', ['login'], 'login [--qr|--agent-qr|--codex|--browser] [--env <name>|--overseas|--yidaapps] [--corp-id <corpId>]', 'help.cmd_login', {
|
|
23
|
+
command('login', ['login'], 'login [--qr|--agent-qr|--codex|--browser] [--env <name>|--intl|--overseas|--global|--yidaapps] [--corp-id <corpId>]', 'help.cmd_login', {
|
|
24
24
|
requiresLogin: false,
|
|
25
25
|
output: 'json',
|
|
26
26
|
}),
|
|
@@ -46,6 +46,9 @@ const COMMAND_GROUPS = [
|
|
|
46
46
|
}),
|
|
47
47
|
command('create-app', ['create-app'], 'create-app "<name>"|--name <name> [options] [--open|--no-open]', 'help.cmd_create_app'),
|
|
48
48
|
command('update-app', ['update-app'], 'update-app <appType> --name "..."', 'help.cmd_update_app'),
|
|
49
|
+
command('app-permission', ['app-permission'], 'app-permission <get|set|add|remove|search-user> ...', 'help.cmd_app_permission', {
|
|
50
|
+
output: 'json',
|
|
51
|
+
}),
|
|
49
52
|
command('export', ['export'], 'export <appType> [output]', 'help.cmd_export'),
|
|
50
53
|
command('import', ['import'], 'import <file> [name]', 'help.cmd_import'),
|
|
51
54
|
],
|
|
@@ -133,6 +136,7 @@ const COMMAND_GROUPS = [
|
|
|
133
136
|
command('integration.create', ['integration', 'create'], 'integration create <appType> ...', 'help.cmd_integration'),
|
|
134
137
|
command('integration.check', ['integration', 'check'], 'integration check <appType...>', 'help.cmd_integration_check'),
|
|
135
138
|
command('dws', ['dws'], 'dws <command> [args]', 'help.cmd_dws'),
|
|
139
|
+
command('dws.contact-user-search', ['dws', 'contact', 'user', 'search'], 'dws contact user search --keyword <text>', 'help.cmd_dws'),
|
|
136
140
|
command('dingtalk-link', ['dingtalk-link'], 'dingtalk-link <url> [--target fullScreen] [--legacy-scheme] [--json]', 'help.cmd_dingtalk_link', {
|
|
137
141
|
requiresLogin: false,
|
|
138
142
|
output: 'text|json',
|
package/lib/core/env-manager.js
CHANGED
|
@@ -65,14 +65,23 @@ function buildDingtalkOAuthLoginUrl(options = {}) {
|
|
|
65
65
|
scope: 'openid corpid',
|
|
66
66
|
lang: options.lang || 'zh_CN',
|
|
67
67
|
});
|
|
68
|
+
if (options.forceLogin) {
|
|
69
|
+
params.set('FEForceLogin', 'true');
|
|
70
|
+
}
|
|
68
71
|
|
|
69
72
|
return `${loginOrigin}/oauth2/auth?${params.toString()}`;
|
|
70
73
|
}
|
|
71
74
|
|
|
75
|
+
// 海外 YiDA / DingTalk International 登录入口。
|
|
76
|
+
// 必须满足三个条件才能让国际版钉钉扫码识别:
|
|
77
|
+
// 1. login origin 为 login.dingtalk.io
|
|
78
|
+
// 2. redirect_uri 落在 www.yidaapps.com(否则登完跳回国内域名,海外后端拿不到 session)
|
|
79
|
+
// 3. 追加 FEForceLogin=true,强制走国际版登录流程
|
|
72
80
|
const INTERNATIONAL_LOGIN_URL = buildDingtalkOAuthLoginUrl({
|
|
73
81
|
loginOrigin: DINGTALK_INTL_LOGIN_ORIGIN,
|
|
74
82
|
baseUrl: INTERNATIONAL_BASE_URL,
|
|
75
83
|
lang: 'en_US',
|
|
84
|
+
forceLogin: true,
|
|
76
85
|
});
|
|
77
86
|
const LEGACY_INTERNATIONAL_LOGIN_URL = buildDingtalkOAuthLoginUrl({
|
|
78
87
|
loginOrigin: DINGTALK_INTL_LOGIN_ORIGIN,
|
|
@@ -96,11 +105,11 @@ const DEFAULT_ALIBABA_INTERNAL_ENV = {
|
|
|
96
105
|
cookieFile: 'cookies-alibaba.json',
|
|
97
106
|
};
|
|
98
107
|
|
|
99
|
-
/** 海外版
|
|
108
|
+
/** 海外版 YiDA / DingTalk International 环境配置 */
|
|
100
109
|
const DEFAULT_INTERNATIONAL_ENV = {
|
|
101
110
|
baseUrl: INTERNATIONAL_BASE_URL,
|
|
102
111
|
loginUrl: INTERNATIONAL_LOGIN_URL,
|
|
103
|
-
description: '海外版 YiDA Apps / DingTalk
|
|
112
|
+
description: '海外版 YiDA Apps / DingTalk International(www.yidaapps.com)',
|
|
104
113
|
cookieFile: 'cookies-intl.json',
|
|
105
114
|
};
|
|
106
115
|
|
|
@@ -115,27 +124,51 @@ const ENV_ALIASES = {
|
|
|
115
124
|
aliyun: 'public',
|
|
116
125
|
domestic: 'public',
|
|
117
126
|
china: 'public',
|
|
127
|
+
'国内': 'public',
|
|
128
|
+
'国内版': 'public',
|
|
129
|
+
'中国': 'public',
|
|
130
|
+
'中国版': 'public',
|
|
131
|
+
'国内宜搭': 'public',
|
|
132
|
+
'中国宜搭': 'public',
|
|
118
133
|
overseas: 'intl',
|
|
119
134
|
oversea: 'intl',
|
|
120
135
|
international: 'intl',
|
|
121
136
|
global: 'intl',
|
|
122
137
|
abroad: 'intl',
|
|
123
138
|
intl: 'intl',
|
|
139
|
+
'海外': 'intl',
|
|
140
|
+
'海外版': 'intl',
|
|
141
|
+
'国际': 'intl',
|
|
142
|
+
'国际版': 'intl',
|
|
143
|
+
'全球': 'intl',
|
|
144
|
+
'全球版': 'intl',
|
|
145
|
+
'海外宜搭': 'intl',
|
|
146
|
+
'海外yida': 'intl',
|
|
147
|
+
'国际宜搭': 'intl',
|
|
148
|
+
'全球宜搭': 'intl',
|
|
149
|
+
'日本': 'intl',
|
|
150
|
+
'日本宜搭': 'intl',
|
|
151
|
+
'日本yida': 'intl',
|
|
124
152
|
alibaba: 'alibaba',
|
|
125
153
|
internal: 'alibaba',
|
|
126
154
|
intranet: 'alibaba',
|
|
155
|
+
'阿里': 'alibaba',
|
|
156
|
+
'阿里内网': 'alibaba',
|
|
157
|
+
'内网': 'alibaba',
|
|
127
158
|
};
|
|
128
159
|
|
|
129
160
|
const SHARED_COOKIE_DOMAINS = new Set([
|
|
130
161
|
'aliwork.com',
|
|
131
162
|
'yidaapps.com',
|
|
132
163
|
'alibaba-inc.com',
|
|
164
|
+
'yidaapps.com',
|
|
133
165
|
]);
|
|
134
166
|
|
|
135
167
|
const KNOWN_YIDA_HOSTS = new Set([
|
|
136
168
|
'www.aliwork.com',
|
|
137
169
|
'www.yidaapps.com',
|
|
138
170
|
'yida-group.alibaba-inc.com',
|
|
171
|
+
'www.yidaapps.com',
|
|
139
172
|
]);
|
|
140
173
|
|
|
141
174
|
function cloneBuiltinEnvironments() {
|
|
@@ -255,6 +288,7 @@ function inferLoginUrlForBaseUrl(baseUrl, fallbackLoginUrl) {
|
|
|
255
288
|
loginOrigin: DINGTALK_INTL_LOGIN_ORIGIN,
|
|
256
289
|
baseUrl: normalizedBaseUrl,
|
|
257
290
|
lang: 'en_US',
|
|
291
|
+
forceLogin: true,
|
|
258
292
|
});
|
|
259
293
|
}
|
|
260
294
|
return fallbackLoginUrl || `${normalizedBaseUrl}/workPlatform`;
|
package/lib/core/locales/ar.js
CHANGED
|
@@ -21,6 +21,7 @@ module.exports = {
|
|
|
21
21
|
cmd_corp_efficiency: 'استعلام عن نظرة عامة على كفاءة المؤسسة وتقارير التفاصيل',
|
|
22
22
|
cmd_create_app: 'إنشاء تطبيق Yida',
|
|
23
23
|
cmd_update_app: 'تحديث معلومات التطبيق',
|
|
24
|
+
cmd_app_permission: 'إدارة مسؤولي التطبيق الأساسيين والبيانات والتطوير',
|
|
24
25
|
cmd_export: 'تصدير التطبيق (حزمة الترحيل)',
|
|
25
26
|
cmd_import: 'استيراد حزمة الترحيل، إعادة بناء التطبيق',
|
|
26
27
|
group_form: 'النماذج & الصفحات',
|
package/lib/core/locales/de.js
CHANGED
|
@@ -21,6 +21,7 @@ module.exports = {
|
|
|
21
21
|
cmd_corp_efficiency: 'Unternehmenseffizienz-Übersicht und Detailberichte abrufen',
|
|
22
22
|
cmd_create_app: 'Yida-App erstellen',
|
|
23
23
|
cmd_update_app: 'App-Informationen aktualisieren',
|
|
24
|
+
cmd_app_permission: 'App-Haupt-, Daten- und Entwicklungsadmins verwalten',
|
|
24
25
|
cmd_export: 'App exportieren (Migrationspaket erstellen)',
|
|
25
26
|
cmd_import: 'Migrationspaket importieren, App neu aufbauen',
|
|
26
27
|
group_form: 'Formulare & Seiten',
|
package/lib/core/locales/en.js
CHANGED
|
@@ -23,6 +23,7 @@ module.exports = {
|
|
|
23
23
|
cmd_corp_efficiency: 'Query enterprise efficiency overview and detail reports',
|
|
24
24
|
cmd_create_app: 'Create a Yida app',
|
|
25
25
|
cmd_update_app: 'Update app info',
|
|
26
|
+
cmd_app_permission: 'Manage app primary, data, and developer admins',
|
|
26
27
|
cmd_export: 'Export app (generate migration package)',
|
|
27
28
|
cmd_import: 'Import migration package, rebuild app',
|
|
28
29
|
group_form: 'Forms & Pages',
|