openxiangda 1.0.246 → 1.0.247

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
@@ -54,6 +54,8 @@ openxiangda automation list --profile dev
54
54
  openxiangda automation executions daily_ticket_digest --profile dev
55
55
  openxiangda automation diagnose daily_ticket_digest --profile dev
56
56
  openxiangda permission role-list --profile dev
57
+ openxiangda permission role-users sales --profile dev
58
+ openxiangda permission role-add-users sales --user-ids <user-id> --change <change-id> --profile dev
57
59
  openxiangda settings get customer --profile dev
58
60
  openxiangda data-view list --profile dev
59
61
  openxiangda data-view status ticket_with_customer --profile dev
package/lib/cli.js CHANGED
@@ -207,6 +207,7 @@ const {
207
207
  writeJson,
208
208
  } = require('./utils');
209
209
  const { version: CURRENT_VERSION } = require('../package.json');
210
+ const { resolveRoleMembershipId } = require('./role-resolution');
210
211
 
211
212
  const NPM_PACKAGE_NAME = 'openxiangda';
212
213
  const OFFICIAL_NPM_REGISTRY = 'https://registry.npmjs.org';
@@ -390,7 +391,7 @@ Usage:
390
391
  openxiangda connector list|get|create|update|upsert|delete|invoke|download-test [--json-file file]
391
392
  openxiangda notification template-list|template-get|template-upsert|template-delete|type-list|type-get|type-upsert|type-delete|preview|send|batch-send
392
393
  openxiangda organization capabilities|department-list|department-get|department-create|department-update|account-list|account-get|account-create|account-update|account-reset-password
393
- openxiangda permission role-list|role-create|role-update|role-delete|role-bind|snapshot|audit
394
+ openxiangda permission role-list|role-create|role-update|role-delete|role-bind|role-users|role-add-users|snapshot|audit
394
395
  openxiangda permission page-group-list|page-group-create|page-group-update|page-group-delete|page-group-bind
395
396
  openxiangda permission form-group-list|form-group-create|form-group-update|form-group-delete|form-group-bind
396
397
  openxiangda settings get|save|indexes|indexes-save|data-management|data-management-save|public-access
@@ -14105,7 +14106,10 @@ async function permission(args) {
14105
14106
  const { flags, positional } = parseArgs(rest);
14106
14107
  if (wantsSubcommandHelp(subcommand, flags)) {
14107
14108
  print(
14108
- '用法: openxiangda permission role-list|role-create|role-update|role-delete|role-bind|role-users|role-add-users|page-group-list|page-group-create|page-group-update|page-group-delete|page-group-bind|form-group-list|form-group-create|form-group-update|form-group-delete|form-group-bind|form-summary|menu-permissions|snapshot|audit'
14109
+ [
14110
+ '用法: openxiangda permission role-list|role-create|role-update|role-delete|role-bind|role-users|role-add-users|page-group-list|page-group-create|page-group-update|page-group-delete|page-group-bind|form-group-list|form-group-create|form-group-update|form-group-delete|form-group-bind|form-summary|menu-permissions|snapshot|audit',
14111
+ 'role-users/role-add-users 接受角色 code 或 UUID;本地未绑定的 code 会从当前应用角色列表精确解析,找不到或不唯一时显式失败。',
14112
+ ].join('\n')
14109
14113
  );
14110
14114
  return;
14111
14115
  }
@@ -14218,7 +14222,12 @@ async function permission(args) {
14218
14222
  const [roleKey] = positional;
14219
14223
  if (!roleKey) fail('用法: openxiangda permission role-users <roleCode|roleId>');
14220
14224
  const target = getWorkspaceTarget(config, profileName, flags);
14221
- const roleId = resolveRoleId(target.bound, roleKey, flags);
14225
+ const roleId = await resolveRoleMembershipIdForCommand(
14226
+ config,
14227
+ target,
14228
+ roleKey,
14229
+ flags
14230
+ );
14222
14231
  const data = await requestWithAuth(
14223
14232
  config,
14224
14233
  target.profileName,
@@ -14243,7 +14252,12 @@ async function permission(args) {
14243
14252
  fail('用法: openxiangda permission role-add-users <roleCode|roleId> --user-ids <id1,id2>');
14244
14253
  }
14245
14254
  const target = getWorkspaceTarget(config, profileName, flags);
14246
- const roleId = resolveRoleId(target.bound, roleKey, flags);
14255
+ const roleId = await resolveRoleMembershipIdForCommand(
14256
+ config,
14257
+ target,
14258
+ roleKey,
14259
+ flags
14260
+ );
14247
14261
  await ensureDirectMutationPublishContext(config, target, flags, 'permission role-add-users');
14248
14262
  const data = await requestWithAuth(
14249
14263
  config,
@@ -17471,6 +17485,34 @@ function resolveRoleId(bound, roleKey, flags = {}) {
17471
17485
  return mapped || roleKey;
17472
17486
  }
17473
17487
 
17488
+ async function resolveRoleMembershipIdForCommand(
17489
+ config,
17490
+ target,
17491
+ roleKey,
17492
+ flags = {}
17493
+ ) {
17494
+ return resolveRoleMembershipId({
17495
+ roleKey,
17496
+ explicitRoleId: flags['role-id'],
17497
+ localRoleId: target.bound.resources?.roles?.[roleKey]?.roleId,
17498
+ listRoles: async roleCode => {
17499
+ const data = await requestWithAuth(
17500
+ config,
17501
+ target.profileName,
17502
+ apiPathWithQuery(
17503
+ `/openxiangda-api/v1/apps/${encodeURIComponent(target.appType)}/roles`,
17504
+ {
17505
+ code: roleCode,
17506
+ page: 1,
17507
+ limit: 200,
17508
+ }
17509
+ )
17510
+ );
17511
+ return normalizeItems(data);
17512
+ },
17513
+ });
17514
+ }
17515
+
17474
17516
  function resolvePagePermissionMenuTargets(bound, flags = {}) {
17475
17517
  const direct = [
17476
17518
  ...splitList(flags['menu-form-uuids']),
@@ -0,0 +1,57 @@
1
+ const ROLE_UUID_PATTERN =
2
+ /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
3
+
4
+ function isRoleUuid(value) {
5
+ return ROLE_UUID_PATTERN.test(String(value || '').trim());
6
+ }
7
+
8
+ async function resolveRoleMembershipId(options = {}) {
9
+ const roleKey = String(options.roleKey || '').trim();
10
+ const explicitRoleId = String(options.explicitRoleId || '').trim();
11
+ const localRoleId = String(options.localRoleId || '').trim();
12
+
13
+ if (explicitRoleId) return explicitRoleId;
14
+ if (isRoleUuid(roleKey)) return roleKey;
15
+ if (localRoleId) return localRoleId;
16
+ if (!roleKey) throw roleResolutionError('ROLE_KEY_REQUIRED', '角色代码或 UUID 不能为空');
17
+ if (typeof options.listRoles !== 'function') {
18
+ throw roleResolutionError('ROLE_LIST_UNAVAILABLE', '无法查询应用角色列表');
19
+ }
20
+
21
+ const roles = await options.listRoles(roleKey);
22
+ const matches = (Array.isArray(roles) ? roles : []).filter(
23
+ role => String(role?.code || role?.roleCode || '').trim() === roleKey
24
+ );
25
+ if (matches.length === 0) {
26
+ throw roleResolutionError(
27
+ 'ROLE_CODE_NOT_FOUND',
28
+ `应用角色代码 ${roleKey} 不存在;请先创建角色或使用 role-bind/角色 UUID`
29
+ );
30
+ }
31
+ if (matches.length > 1) {
32
+ throw roleResolutionError(
33
+ 'ROLE_CODE_AMBIGUOUS',
34
+ `应用角色代码 ${roleKey} 匹配到 ${matches.length} 个角色,无法安全确定 role UUID`
35
+ );
36
+ }
37
+
38
+ const roleId = String(matches[0]?.id || matches[0]?.roleId || '').trim();
39
+ if (!roleId) {
40
+ throw roleResolutionError(
41
+ 'ROLE_ID_MISSING',
42
+ `应用角色代码 ${roleKey} 的查询结果缺少 role UUID`
43
+ );
44
+ }
45
+ return roleId;
46
+ }
47
+
48
+ function roleResolutionError(code, message) {
49
+ const error = new Error(`${code}: ${message}`);
50
+ error.code = code;
51
+ return error;
52
+ }
53
+
54
+ module.exports = {
55
+ isRoleUuid,
56
+ resolveRoleMembershipId,
57
+ };
@@ -80,6 +80,11 @@ Use role codes in permission group JSON. Bind existing role IDs only inside the
80
80
  openxiangda permission role-bind sales --role-id <id> --profile dev
81
81
  ```
82
82
 
83
+ `role-users` and `role-add-users` accept either a role UUID or a logical role
84
+ code. When a code has no profile-local binding, the CLI resolves it from the
85
+ current application's role list and requires exactly one matching code; missing
86
+ or ambiguous matches fail explicitly instead of sending the code as a role ID.
87
+
83
88
  For dynamic multi-role apps, do not hardcode role behavior only in page code. Create a role maintenance form and sync it to platform roles with automation / JS_CODE. Visible scope fields should be maintainable controls: personnel/department fields for people and orgs, `SelectField` / `RadioField` for enums, and `SelectField` with `optionSource.type: "linkedForm"` for records maintained by other forms. When the source form can have many records, set `remoteSearch: true` and `searchFieldId` so typing in the dropdown triggers an SDK query instead of loading every record. If form permission groups need scalar matching, derive hidden keys such as `collegeScopeKey`, `classScopeKey`, `ownerDeptScopeKey`, `ownerUserScopeKey`, and `roleCode`; do not expose raw ID text fields to users. Use page permission groups for entry visibility and form permission groups with condition-based data permissions for real data isolation.
84
89
 
85
90
  For managed platform account governance, use organization/account forms and sync
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openxiangda",
3
- "version": "1.0.246",
3
+ "version": "1.0.247",
4
4
  "description": "OpenXiangda CLI, workspace build tools, runtime SDK, and form components.",
5
5
  "private": false,
6
6
  "bin": {
@@ -90,6 +90,7 @@
90
90
  "test:design-gates": "node scripts/design-gates-smoke.mjs",
91
91
  "test:resource-cli": "node scripts/resource-cli-smoke.mjs",
92
92
  "test:permission-snapshot": "node scripts/permission-snapshot-smoke.mjs",
93
+ "test:permission-role-users": "node scripts/permission-role-users-smoke.mjs",
93
94
  "test:sdk-bundle": "node scripts/sdk-browser-bundle-smoke.mjs",
94
95
  "test:sdk-list-response": "vitest run packages/sdk/src/runtime/react/admin-list/dataSources.test.ts packages/sdk/src/components/core/dataManagementApi.test.ts",
95
96
  "test:api-contract": "node scripts/openxiangda-api-contract-smoke.mjs",