openyida 2026.5.17-1 → 2026.5.18

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 CHANGED
@@ -276,6 +276,7 @@ Environment selectors such as `--env intl`, `--intl`, `--overseas`, `--global`,
276
276
  | `openyida corp-efficiency [overview\|details\|detail\|groups\|notify] [options] [--open\|--no-open]` | Query enterprise efficiency metrics, detail report entries, and related notification actions |
277
277
  | `openyida create-app "<name>"\|--name <name> [options] [--open\|--no-open]` | Create an application and output `appType` |
278
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 |
279
280
  | `openyida export <appType> [output]` | Export an application migration package |
280
281
  | `openyida import <file> [name]` | Import a migration package into a target environment |
281
282
 
@@ -445,13 +446,15 @@ Scan the QR code to join the OpenYida DingTalk user group for updates and suppor
445
446
 
446
447
  Thanks to everyone who has contributed to OpenYida. Read the [Contributing Guide](./CONTRIBUTING.md) to get involved.
447
448
 
449
+ Latest contributors: [DDlixin1](https://github.com/DDlixin1), [fcloud](https://github.com/fcloud).
450
+
448
451
  <!-- openyida-contributors:start -->
449
452
 
450
453
  <p>
451
454
  <a href="https://github.com/yize"><img src="https://github.com/yize.png?size=48" width="48" height="48" alt="九神" title="九神" /></a>
452
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>
453
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>
454
- <a href="https://github.com/fcloud"><img src="https://github.com/fcloud.png?size=48" width="48" height="48" alt="Aiden Wu" title="Aiden Wu" /></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>
455
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>
456
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>
457
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
+ };
@@ -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
  ],
@@ -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: 'النماذج & الصفحات',
@@ -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',
@@ -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',
@@ -21,6 +21,7 @@ module.exports = {
21
21
  cmd_corp_efficiency: 'Consultar resumen de eficiencia empresarial e informes detallados',
22
22
  cmd_create_app: 'Crear una aplicación Yida',
23
23
  cmd_update_app: 'Actualizar información de la aplicación',
24
+ cmd_app_permission: 'Gestionar admins principales, de datos y desarrollo de la app',
24
25
  cmd_export: 'Exportar aplicación (paquete de migración)',
25
26
  cmd_import: 'Importar paquete de migración, reconstruir app',
26
27
  group_form: 'Formularios & Páginas',
@@ -21,6 +21,7 @@ module.exports = {
21
21
  cmd_corp_efficiency: 'Consulter l\'aperçu de l\'efficacité entreprise et les rapports détaillés',
22
22
  cmd_create_app: 'Créer une application Yida',
23
23
  cmd_update_app: 'Mettre à jour les infos de l\'application',
24
+ cmd_app_permission: 'Gérer les admins principaux, données et développement de l\'app',
24
25
  cmd_export: 'Exporter l\'application (package de migration)',
25
26
  cmd_import: 'Importer un package de migration',
26
27
  group_form: 'Formulaires & Pages',
@@ -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: 'फॉर्म & पेज',
@@ -23,6 +23,7 @@ module.exports = {
23
23
  cmd_corp_efficiency: '企業効率の概要と明細レポートを取得',
24
24
  cmd_create_app: '宜搭アプリを作成',
25
25
  cmd_update_app: 'アプリ情報を更新',
26
+ cmd_app_permission: 'アプリ主管理者、データ管理者、開発メンバーを管理',
26
27
  cmd_export: 'アプリをエクスポート(移行パッケージ生成)',
27
28
  cmd_import: '移行パッケージをインポート、アプリを再構築',
28
29
  group_form: 'フォーム & ページ',
@@ -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: '양식 & 페이지',
@@ -21,6 +21,7 @@ module.exports = {
21
21
  cmd_corp_efficiency: 'Consultar visão geral de eficiência empresarial e relatórios detalhados',
22
22
  cmd_create_app: 'Criar um aplicativo Yida',
23
23
  cmd_update_app: 'Atualizar informações do aplicativo',
24
+ cmd_app_permission: 'Gerenciar admins principais, de dados e desenvolvimento do app',
24
25
  cmd_export: 'Exportar aplicativo (pacote de migração)',
25
26
  cmd_import: 'Importar pacote de migração, reconstruir app',
26
27
  group_form: 'Formulários & Páginas',
@@ -21,6 +21,7 @@ module.exports = {
21
21
  cmd_corp_efficiency: 'Truy vấn tổng quan hiệu quả doanh nghiệp và báo cáo chi tiết',
22
22
  cmd_create_app: 'Tạo ứng dụng Yida',
23
23
  cmd_update_app: 'Cập nhật thông tin ứng dụng',
24
+ cmd_app_permission: 'Quản lý quản trị chính, dữ liệu và phát triển của ứng dụng',
24
25
  cmd_export: 'Xuất ứng dụng (tạo gói di chuyển)',
25
26
  cmd_import: 'Nhập gói di chuyển, xây dựng lại ứng dụng',
26
27
  group_form: 'Biểu mẫu & Trang',
@@ -23,6 +23,7 @@ module.exports = {
23
23
  cmd_corp_efficiency: '查詢企業效能概覽和明細報表',
24
24
  cmd_create_app: '建立宜搭應用程式',
25
25
  cmd_update_app: '更新應用程式資料',
26
+ cmd_app_permission: '管理應用主管理員、資料管理員和開發成員',
26
27
  cmd_export: '匯出應用程式(產生遷移包)',
27
28
  cmd_import: '匯入遷移包,重建應用程式',
28
29
  group_form: '表單 & 頁面',
@@ -23,6 +23,7 @@ module.exports = {
23
23
  cmd_corp_efficiency: '查询企业效能概览和明细报表',
24
24
  cmd_create_app: '创建宜搭应用',
25
25
  cmd_update_app: '更新应用信息',
26
+ cmd_app_permission: '管理应用主管理员、数据管理员和开发成员',
26
27
  cmd_export: '导出应用(生成迁移包)',
27
28
  cmd_import: '导入迁移包,重建应用',
28
29
  group_form: '表单 & 页面',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openyida",
3
- "version": "2026.5.17-1",
3
+ "version": "2026.5.18",
4
4
  "description": "OpenYida CLI - 宜搭低代码 AI 开发工具(安装即用,零配置)",
5
5
  "bin": {
6
6
  "openyida": "bin/yida.js",
@@ -12,6 +12,7 @@ const SKILL_COVERAGE = {
12
12
  'large-file-write': { level: 'offline', tests: ['write fixture generation uses fs APIs in runner tests'] },
13
13
  'sls-log-workbench': { level: 'offline-unit', tests: ['skill metadata and packaging validation'], reason: 'internal support-only SLS tooling is guarded by passphrase and corp whitelist; shared real E2E must not query production logs' },
14
14
  'yida-app': { level: 'real-e2e', stages: ['app', 'form', 'page', 'data', 'report', 'dashboard'] },
15
+ 'yida-app-permission': { level: 'offline-unit', tests: ['tests/app-permission.test.js'], reason: 'app admin mutations affect real application access; shared real E2E only validates safe read paths' },
15
16
  'yida-basic-info': { level: 'offline-unit', tests: ['tests/basic-info.test.js'], reason: 'basic-info reads org admin metadata and can update domains; unit coverage avoids mutating shared real org settings' },
16
17
  'yida-chart': { level: 'real-e2e', stages: ['report', 'dashboard'], tests: ['report chart config generation'] },
17
18
  'yida-connector': { level: 'offline', stages: ['connector-local'], commands: ['connector gen-template', 'connector parse-api'] },
@@ -172,6 +172,7 @@ openyida copy
172
172
  | `yida-page-config` | `skills/yida-page-config/SKILL.md` | 页面公开访问/组织内分享配置 | `openyida verify-short-url <appType> <formUuid> <url>` |
173
173
  | `yida-basic-info` | `skills/yida-basic-info/SKILL.md` | 组织基本信息、资源容量、额度和域名设置查询 | `openyida basic-info overview` |
174
174
  | `yida-form-permission` | `skills/yida-form-permission/SKILL.md` | 表单权限查询与保存 | `openyida get-permission <appType> <formUuid>` |
175
+ | `yida-app-permission` | `skills/yida-app-permission/SKILL.md` | 应用级管理员设置(主管理员/数据管理员/开发成员) | `openyida app-permission get <appType>` |
175
176
  | `yida-corp-manager` | `skills/yida-corp-manager/SKILL.md` | 平台管理员、应用管理员、子管理员与通讯录权限 | `openyida corp-manager <子命令>` |
176
177
  | `yida-form-detail` | `skills/yida-form-detail/SKILL.md` | 表单详情页 formDetail 样式优化 | 详见 SKILL.md |
177
178
  | `yida-data-management` | `skills/yida-data-management/SKILL.md` | 表单/流程/任务数据查询与变更 | `openyida data query form <appType> <formUuid>` |
@@ -0,0 +1,89 @@
1
+ ---
2
+ name: yida-app-permission
3
+ description: 应用级管理员设置。查询和维护单个宜搭应用的应用主管理员、数据管理员、开发成员。不适用于:平台级管理员(应使用 yida-corp-manager),或表单权限组/数据范围(应使用 yida-form-permission)。
4
+ ---
5
+
6
+ # 应用级权限设置
7
+
8
+ ## 严格要求 (MUST DO)
9
+
10
+ - 修改前先执行 `openyida app-permission get <appType>` 查询当前应用管理员设置。
11
+ - 添加成员前先用 `search-user` 确认目标人员 userId;同名人员必须结合部门信息区分。
12
+ - 修改 `main` 应用主管理员会影响应用后台最高管理权限,执行前必须展示当前成员和修改后成员,并等待用户确认。
13
+ - `main` 应用主管理员不能为空;清空仅允许 `data` 或 `dev`。
14
+
15
+ ## 不适用场景
16
+
17
+ - 平台管理员、平台子管理员、应用创建权限:使用 `yida-corp-manager`。
18
+ - 表单权限组、数据权限、操作权限:使用 `yida-form-permission`。
19
+ - 页面公开访问或组织内分享:使用 `yida-page-config`。
20
+ - 流程节点字段权限:使用 `yida-process-rule`。
21
+
22
+ ## 常用命令
23
+
24
+ 查询应用管理员设置:
25
+
26
+ ```bash
27
+ openyida app-permission get <appType>
28
+ ```
29
+
30
+ 搜索人员,确认 userId:
31
+
32
+ ```bash
33
+ openyida app-permission search-user "姓名或关键词" --dept "部门关键词"
34
+ ```
35
+
36
+ 添加成员:
37
+
38
+ ```bash
39
+ openyida app-permission add <appType> main --users <userId>
40
+ openyida app-permission add <appType> data --users <userId1,userId2>
41
+ openyida app-permission add <appType> dev --users <userId1,userId2>
42
+ ```
43
+
44
+ 完全替换某一类成员:
45
+
46
+ ```bash
47
+ openyida app-permission set <appType> main --users <userId1,userId2>
48
+ openyida app-permission set <appType> data --users <userId1,userId2>
49
+ openyida app-permission set <appType> dev --users <userId1,userId2>
50
+ ```
51
+
52
+ 移除成员:
53
+
54
+ ```bash
55
+ openyida app-permission remove <appType> data --users <userId>
56
+ openyida app-permission remove <appType> dev --users <userId>
57
+ ```
58
+
59
+ 清空数据管理员或开发成员:
60
+
61
+ ```bash
62
+ openyida app-permission set <appType> data --clear
63
+ openyida app-permission set <appType> dev --clear
64
+ ```
65
+
66
+ ## 角色映射
67
+
68
+ | CLI 角色 | 页面含义 | 接口 adminType |
69
+ |---------|----------|----------------|
70
+ | `main` | 应用主管理员 | `MAIN` |
71
+ | `data` | 数据管理员 | `DATA` |
72
+ | `dev` | 开发成员 | `DEV` |
73
+
74
+ ## 安全检查清单
75
+
76
+ 1. `app-permission search-user` 确认目标 userId 和人员身份。
77
+ 2. `app-permission get` 查询当前应用管理员配置。
78
+ 3. 展示将要执行的 `add` / `remove` / `set` 命令和修改后 userId 列表。
79
+ 4. 用户确认后执行修改。
80
+ 5. 再次 `app-permission get` 验证结果。
81
+
82
+ ## 异常处理
83
+
84
+ | 异常场景 | 处理方式 |
85
+ |---------|----------|
86
+ | 权限不足 / 登录态失效 | 停止执行,提示用户执行 `openyida login` 重新登录或切换具备权限的账号 |
87
+ | 同名人员 | 必须通过部门路径或 userId 二次确认 |
88
+ | 试图清空应用主管理员 | 拒绝执行,要求至少保留 1 个 `main` 成员 |
89
+ | 修改前未查询现有配置 | 先执行 `app-permission get`,不要直接修改 |