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
|
-
|
|
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 =
|
|
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 =
|
|
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.
|
|
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",
|