openxiangda 1.0.135 → 1.0.137
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/lib/cli.js +576 -3
- package/lib/design-gates.js +48 -10
- package/openxiangda-skills/SKILL.md +7 -1
- package/openxiangda-skills/references/best-practices.md +48 -10
- package/openxiangda-skills/references/openxiangda-api.md +32 -6
- package/openxiangda-skills/references/permission-design-patterns.md +277 -0
- package/openxiangda-skills/references/permissions-settings.md +186 -0
- package/openxiangda-skills/references/resource-manifest-cheatsheet.md +119 -1
- package/openxiangda-skills/skills/openxiangda-architecture-design/SKILL.md +8 -0
- package/openxiangda-skills/skills/openxiangda-permission-settings/SKILL.md +38 -1
- package/package.json +1 -1
- package/packages/sdk/dist/runtime/react.d.mts +3 -1
- package/packages/sdk/dist/runtime/react.d.ts +3 -1
- package/templates/openxiangda-react-spa/.cursor/rules/openxiangda.mdc +7 -1
- package/templates/openxiangda-react-spa/.qoder/rules/openxiangda.md +7 -1
- package/templates/openxiangda-react-spa/AGENTS.md +6 -1
- package/templates/sy-lowcode-app-workspace/.cursor/rules/openxiangda-resources.mdc +5 -1
- package/templates/sy-lowcode-app-workspace/.cursor/rules/openxiangda.mdc +6 -1
- package/templates/sy-lowcode-app-workspace/.qoder/rules/openxiangda-resources.md +6 -0
- package/templates/sy-lowcode-app-workspace/.qoder/rules/openxiangda.md +6 -1
- package/templates/sy-lowcode-app-workspace/AGENTS.md +6 -1
- package/templates/sy-lowcode-app-workspace/examples/best-practices/README.md +3 -0
- package/templates/sy-lowcode-app-workspace/examples/best-practices/access-governance.md +239 -0
- package/templates/sy-lowcode-app-workspace/examples/best-practices/catalog.json +6 -0
- package/templates/sy-lowcode-app-workspace/examples/best-practices/decision-guide.md +25 -1
package/lib/cli.js
CHANGED
|
@@ -93,6 +93,7 @@ async function main(argv) {
|
|
|
93
93
|
if (command === 'workflow') return workflow(rest);
|
|
94
94
|
if (command === 'automation') return automation(rest);
|
|
95
95
|
if (command === 'data-view') return dataView(rest);
|
|
96
|
+
if (command === 'scope') return scope(rest);
|
|
96
97
|
if (command === 'route') return route(rest);
|
|
97
98
|
if (command === 'public-access') return publicAccess(rest);
|
|
98
99
|
if (command === 'auth-config') return authConfig(rest);
|
|
@@ -162,6 +163,7 @@ Usage:
|
|
|
162
163
|
openxiangda automation diagnose <automationCode|automationId> [--redact] [--json]
|
|
163
164
|
openxiangda automation publish|enable|disable <automationCode|automationId>
|
|
164
165
|
openxiangda data-view list|get|create|update|upsert|delete|status|refresh|query|stats <dataViewCode> [--profile name] [--json]
|
|
166
|
+
openxiangda scope dimension-list|dimension-upsert|grant-source-list|grant-source-upsert|policy-list|policy-upsert|sync|explain [code] [--json-file file]
|
|
165
167
|
openxiangda route list|get|create|update|upsert|delete [--json-file file] [--dry-run] [--write-manifest]
|
|
166
168
|
openxiangda public-access list|get|create|update|upsert|delete|ticket-create|session-test|grant-check [--json-file file]
|
|
167
169
|
openxiangda auth-config list|get|create|update|upsert|delete|methods [--json-file file]
|
|
@@ -1860,6 +1862,9 @@ async function workspace(args) {
|
|
|
1860
1862
|
connectors: {},
|
|
1861
1863
|
dataViews: {},
|
|
1862
1864
|
storageConfigs: {},
|
|
1865
|
+
scopeDimensions: {},
|
|
1866
|
+
scopeGrantSources: {},
|
|
1867
|
+
dataScopePolicies: {},
|
|
1863
1868
|
pagePermissionGroups: {},
|
|
1864
1869
|
formPermissionGroups: {},
|
|
1865
1870
|
formSettings: {},
|
|
@@ -3101,6 +3106,112 @@ async function dataView(args) {
|
|
|
3101
3106
|
fail('用法: openxiangda data-view list|get|create|update|upsert|delete|status|refresh|query|stats');
|
|
3102
3107
|
}
|
|
3103
3108
|
|
|
3109
|
+
async function scope(args) {
|
|
3110
|
+
const { subcommand, rest } = parseSubcommandArgs(args);
|
|
3111
|
+
const { flags, positional } = parseArgs(rest);
|
|
3112
|
+
if (wantsSubcommandHelp(subcommand, flags)) {
|
|
3113
|
+
print('用法: openxiangda scope dimension-list|dimension-upsert|grant-source-list|grant-source-upsert|policy-list|policy-upsert|sync|explain [code] [--json-file file] [--dry-run] [--write-manifest]');
|
|
3114
|
+
return;
|
|
3115
|
+
}
|
|
3116
|
+
const config = loadConfig();
|
|
3117
|
+
const target = getWorkspaceTarget(config, flags.profile || config.currentProfile, flags);
|
|
3118
|
+
const basePath = `/openxiangda-api/v1/apps/${encodeURIComponent(target.appType)}/scope`;
|
|
3119
|
+
|
|
3120
|
+
if (subcommand === 'dimension-list') {
|
|
3121
|
+
return runDirectRequest(config, target, flags, {
|
|
3122
|
+
method: 'GET',
|
|
3123
|
+
path: `${basePath}/dimensions`,
|
|
3124
|
+
});
|
|
3125
|
+
}
|
|
3126
|
+
|
|
3127
|
+
if (subcommand === 'dimension-upsert') {
|
|
3128
|
+
return upsertScopeResource(config, target, flags, positional, {
|
|
3129
|
+
commandName: 'scope dimension-upsert',
|
|
3130
|
+
resourceType: 'scope-dimension',
|
|
3131
|
+
path: `${basePath}/dimensions`,
|
|
3132
|
+
normalizeBody: body => normalizeScopeDimensionManifest(body, target.bound),
|
|
3133
|
+
});
|
|
3134
|
+
}
|
|
3135
|
+
|
|
3136
|
+
if (subcommand === 'grant-source-list') {
|
|
3137
|
+
return runDirectRequest(config, target, flags, {
|
|
3138
|
+
method: 'GET',
|
|
3139
|
+
path: `${basePath}/grant-sources`,
|
|
3140
|
+
});
|
|
3141
|
+
}
|
|
3142
|
+
|
|
3143
|
+
if (subcommand === 'grant-source-upsert') {
|
|
3144
|
+
return upsertScopeResource(config, target, flags, positional, {
|
|
3145
|
+
commandName: 'scope grant-source-upsert',
|
|
3146
|
+
resourceType: 'scope-grant-source',
|
|
3147
|
+
path: `${basePath}/grant-sources`,
|
|
3148
|
+
normalizeBody: body => normalizeScopeGrantSourceManifest(body, target.bound),
|
|
3149
|
+
});
|
|
3150
|
+
}
|
|
3151
|
+
|
|
3152
|
+
if (subcommand === 'policy-list') {
|
|
3153
|
+
return runDirectRequest(config, target, flags, {
|
|
3154
|
+
method: 'GET',
|
|
3155
|
+
path: `${basePath}/policies`,
|
|
3156
|
+
});
|
|
3157
|
+
}
|
|
3158
|
+
|
|
3159
|
+
if (subcommand === 'policy-upsert') {
|
|
3160
|
+
return upsertScopeResource(config, target, flags, positional, {
|
|
3161
|
+
commandName: 'scope policy-upsert',
|
|
3162
|
+
resourceType: 'data-scope-policy',
|
|
3163
|
+
path: `${basePath}/policies`,
|
|
3164
|
+
normalizeBody: normalizeDataScopePolicyManifest,
|
|
3165
|
+
});
|
|
3166
|
+
}
|
|
3167
|
+
|
|
3168
|
+
if (subcommand === 'sync') {
|
|
3169
|
+
const [code] = positional;
|
|
3170
|
+
if (!code) fail('用法: openxiangda scope sync <grantSourceCode> [--dry-run] [--json]');
|
|
3171
|
+
const body = {
|
|
3172
|
+
...readDirectJsonBody(flags, 'scope sync', { optional: true }),
|
|
3173
|
+
...(flags['dry-run'] ? { dryRun: true } : {}),
|
|
3174
|
+
};
|
|
3175
|
+
return runDirectRequest(config, target, flags, {
|
|
3176
|
+
method: 'POST',
|
|
3177
|
+
path: `${basePath}/grant-sources/${encodeURIComponent(code)}/sync`,
|
|
3178
|
+
body,
|
|
3179
|
+
});
|
|
3180
|
+
}
|
|
3181
|
+
|
|
3182
|
+
if (subcommand === 'explain') {
|
|
3183
|
+
const [code] = positional;
|
|
3184
|
+
if (!code) fail('用法: openxiangda scope explain <policyCode> [--json-file file] [--json]');
|
|
3185
|
+
const body = readDirectJsonBody(flags, 'scope explain', { optional: true });
|
|
3186
|
+
return runDirectRequest(config, target, flags, {
|
|
3187
|
+
method: 'POST',
|
|
3188
|
+
path: `${basePath}/policies/${encodeURIComponent(code)}/explain`,
|
|
3189
|
+
body,
|
|
3190
|
+
});
|
|
3191
|
+
}
|
|
3192
|
+
|
|
3193
|
+
fail('用法: openxiangda scope dimension-list|dimension-upsert|grant-source-list|grant-source-upsert|policy-list|policy-upsert|sync|explain');
|
|
3194
|
+
}
|
|
3195
|
+
|
|
3196
|
+
async function upsertScopeResource(config, target, flags, positional, spec) {
|
|
3197
|
+
const rawBody = readDirectJsonBody(flags, spec.commandName);
|
|
3198
|
+
const explicitCode = positional[0] || flags.code;
|
|
3199
|
+
if (explicitCode && !rawBody.code && !rawBody.resourceCode) rawBody.code = explicitCode;
|
|
3200
|
+
const code = explicitCode || rawBody.code || rawBody.resourceCode;
|
|
3201
|
+
if (!code) fail(`openxiangda ${spec.commandName} 缺少资源 code`);
|
|
3202
|
+
const body = spec.normalizeBody(rawBody);
|
|
3203
|
+
const data = await runDirectRequest(config, target, flags, {
|
|
3204
|
+
method: 'POST',
|
|
3205
|
+
path: `${spec.path}/${encodeURIComponent(code)}`,
|
|
3206
|
+
body,
|
|
3207
|
+
}, { returnData: true });
|
|
3208
|
+
if (!flags['dry-run']) {
|
|
3209
|
+
saveScopeResource(target, resourceTypeToBucket(spec.resourceType), code, data);
|
|
3210
|
+
if (flags['write-manifest']) writeDirectManifest(spec.resourceType, code, { ...rawBody, code });
|
|
3211
|
+
}
|
|
3212
|
+
return outputDirectResult({ action: 'upsert', data }, flags);
|
|
3213
|
+
}
|
|
3214
|
+
|
|
3104
3215
|
async function route(args) {
|
|
3105
3216
|
const { subcommand, rest } = parseSubcommandArgs(args);
|
|
3106
3217
|
const { flags, positional } = parseArgs(rest);
|
|
@@ -5579,6 +5690,9 @@ function directManifestDir(resourceType) {
|
|
|
5579
5690
|
role: path.join('src', 'resources', 'roles'),
|
|
5580
5691
|
'page-permission-group': path.join('src', 'resources', 'permissions', 'page-groups'),
|
|
5581
5692
|
'form-permission-group': path.join('src', 'resources', 'permissions', 'form-groups'),
|
|
5693
|
+
'scope-dimension': path.join('src', 'resources', 'permissions', 'scope-dimensions'),
|
|
5694
|
+
'scope-grant-source': path.join('src', 'resources', 'permissions', 'scope-grant-sources'),
|
|
5695
|
+
'data-scope-policy': path.join('src', 'resources', 'permissions', 'data-scope-policies'),
|
|
5582
5696
|
};
|
|
5583
5697
|
return dirs[resourceType];
|
|
5584
5698
|
}
|
|
@@ -5738,6 +5852,8 @@ async function buildPermissionAudit(config, target, flags = {}) {
|
|
|
5738
5852
|
const manifest = loadWorkspaceResources();
|
|
5739
5853
|
const validation = validateWorkspaceResources(manifest);
|
|
5740
5854
|
const roleCodes = new Set((manifest.roles || []).map(item => item.code || item.resourceCode).filter(Boolean));
|
|
5855
|
+
const policyCodes = new Set((manifest.dataScopePolicies || []).map(item => item.code || item.resourceCode).filter(Boolean));
|
|
5856
|
+
const scopePolicyRefs = [];
|
|
5741
5857
|
const warnings = [...validation.warnings];
|
|
5742
5858
|
const errors = [...validation.errors];
|
|
5743
5859
|
for (const group of manifest.pagePermissionGroups || []) {
|
|
@@ -5751,6 +5867,31 @@ async function buildPermissionAudit(config, target, flags = {}) {
|
|
|
5751
5867
|
}
|
|
5752
5868
|
const formUuid = resolveManifestFormUuid(target.bound, group);
|
|
5753
5869
|
if (!formUuid) errors.push(`form permission group ${group.code} 缺少 formCode/formUuid`);
|
|
5870
|
+
if (group.dataPermission?.type === 'scope_policy') {
|
|
5871
|
+
const policyCode = getScopePolicyCode(group.dataPermission);
|
|
5872
|
+
if (policyCode) scopePolicyRefs.push(policyCode);
|
|
5873
|
+
if (policyCode && policyCodes.size > 0 && !policyCodes.has(policyCode)) {
|
|
5874
|
+
warnings.push(`form permission group ${group.code} 引用未声明 data-scope-policy: ${policyCode}`);
|
|
5875
|
+
}
|
|
5876
|
+
}
|
|
5877
|
+
}
|
|
5878
|
+
for (const dataView of manifest.dataViews || []) {
|
|
5879
|
+
for (const group of dataView.permissionGroups || []) {
|
|
5880
|
+
for (const roleCode of group.roles || []) {
|
|
5881
|
+
if (roleCodes.size > 0 && !roleCodes.has(roleCode)) {
|
|
5882
|
+
warnings.push(`data view ${dataView.code} permission group ${group.code || group.name || '?'} 引用未声明角色: ${roleCode}`);
|
|
5883
|
+
}
|
|
5884
|
+
}
|
|
5885
|
+
if (group.dataPermission?.type !== 'scope_policy') continue;
|
|
5886
|
+
const policyCode = getScopePolicyCode(group.dataPermission);
|
|
5887
|
+
if (policyCode) scopePolicyRefs.push(policyCode);
|
|
5888
|
+
if (policyCode && policyCodes.size > 0 && !policyCodes.has(policyCode)) {
|
|
5889
|
+
warnings.push(`data view ${dataView.code} permission group ${group.code || group.name || '?'} 引用未声明 data-scope-policy: ${policyCode}`);
|
|
5890
|
+
}
|
|
5891
|
+
}
|
|
5892
|
+
}
|
|
5893
|
+
if (scopePolicyRefs.length > 0 && (manifest.scopeGrantSources || []).length === 0) {
|
|
5894
|
+
warnings.push('权限组已引用 scope_policy,但本工作区未声明 scopeGrantSources;请确认授权来源由其他工作区或平台后台维护');
|
|
5754
5895
|
}
|
|
5755
5896
|
for (const policy of manifest.publicAccessPolicies || []) {
|
|
5756
5897
|
const grants = normalizePublicAccessPolicyManifest(target.bound, policy).grants || {};
|
|
@@ -5770,6 +5911,12 @@ async function buildPermissionAudit(config, target, flags = {}) {
|
|
|
5770
5911
|
),
|
|
5771
5912
|
errors,
|
|
5772
5913
|
warnings,
|
|
5914
|
+
scope: {
|
|
5915
|
+
dimensions: (manifest.scopeDimensions || []).length,
|
|
5916
|
+
grantSources: (manifest.scopeGrantSources || []).length,
|
|
5917
|
+
policies: (manifest.dataScopePolicies || []).length,
|
|
5918
|
+
referencedPolicies: unique(scopePolicyRefs),
|
|
5919
|
+
},
|
|
5773
5920
|
};
|
|
5774
5921
|
if (flags.live) {
|
|
5775
5922
|
result.live = await requestWithAuth(
|
|
@@ -5806,6 +5953,9 @@ function ensureResourceBuckets(bound) {
|
|
|
5806
5953
|
bound.resources.authConfigs = bound.resources.authConfigs || {};
|
|
5807
5954
|
bound.resources.routes = bound.resources.routes || {};
|
|
5808
5955
|
bound.resources.publicAccessPolicies = bound.resources.publicAccessPolicies || {};
|
|
5956
|
+
bound.resources.scopeDimensions = bound.resources.scopeDimensions || {};
|
|
5957
|
+
bound.resources.scopeGrantSources = bound.resources.scopeGrantSources || {};
|
|
5958
|
+
bound.resources.dataScopePolicies = bound.resources.dataScopePolicies || {};
|
|
5809
5959
|
bound.resources.notifications = bound.resources.notifications || {};
|
|
5810
5960
|
bound.resources.notifications.templates = bound.resources.notifications.templates || {};
|
|
5811
5961
|
bound.resources.notifications.typeConfigs = bound.resources.notifications.typeConfigs || {};
|
|
@@ -6056,6 +6206,24 @@ function saveStorageConfigResource(target, storageCode, storageConfigId, extra =
|
|
|
6056
6206
|
}, keys);
|
|
6057
6207
|
}
|
|
6058
6208
|
|
|
6209
|
+
function saveScopeResource(target, bucket, code, data = {}) {
|
|
6210
|
+
if (!bucket || !code) return;
|
|
6211
|
+
const keys = ['id', 'resourceCode', 'name', 'updatedAt'];
|
|
6212
|
+
saveStateResource(target, bucket, code, {
|
|
6213
|
+
id: data?.id,
|
|
6214
|
+
resourceCode: data?.resourceCode || data?.code || code,
|
|
6215
|
+
name: data?.name,
|
|
6216
|
+
}, keys);
|
|
6217
|
+
}
|
|
6218
|
+
|
|
6219
|
+
function resourceTypeToBucket(resourceType) {
|
|
6220
|
+
return {
|
|
6221
|
+
'scope-dimension': 'scopeDimensions',
|
|
6222
|
+
'scope-grant-source': 'scopeGrantSources',
|
|
6223
|
+
'data-scope-policy': 'dataScopePolicies',
|
|
6224
|
+
}[resourceType];
|
|
6225
|
+
}
|
|
6226
|
+
|
|
6059
6227
|
function saveRouteResource(target, routeCode, routeId, extra = {}) {
|
|
6060
6228
|
const keys = ['routeId', 'pathPattern', 'publicAccess', 'publicPolicyCode'];
|
|
6061
6229
|
saveStateResource(target, 'routes', routeCode, {
|
|
@@ -6208,6 +6376,24 @@ const RESOURCE_SPECS = [
|
|
|
6208
6376
|
topFiles: [path.join('permissions', 'form-groups.json')],
|
|
6209
6377
|
pluralKeys: ['formPermissionGroups', 'formGroups'],
|
|
6210
6378
|
},
|
|
6379
|
+
{
|
|
6380
|
+
key: 'scopeDimensions',
|
|
6381
|
+
dir: path.join('permissions', 'scope-dimensions'),
|
|
6382
|
+
topFiles: [path.join('permissions', 'scope-dimensions.json')],
|
|
6383
|
+
pluralKeys: ['scopeDimensions', 'dimensions'],
|
|
6384
|
+
},
|
|
6385
|
+
{
|
|
6386
|
+
key: 'scopeGrantSources',
|
|
6387
|
+
dir: path.join('permissions', 'scope-grant-sources'),
|
|
6388
|
+
topFiles: [path.join('permissions', 'scope-grant-sources.json')],
|
|
6389
|
+
pluralKeys: ['scopeGrantSources', 'grantSources', 'sources'],
|
|
6390
|
+
},
|
|
6391
|
+
{
|
|
6392
|
+
key: 'dataScopePolicies',
|
|
6393
|
+
dir: path.join('permissions', 'data-scope-policies'),
|
|
6394
|
+
topFiles: [path.join('permissions', 'data-scope-policies.json')],
|
|
6395
|
+
pluralKeys: ['dataScopePolicies', 'scopePolicies', 'policies'],
|
|
6396
|
+
},
|
|
6211
6397
|
{
|
|
6212
6398
|
key: 'formSettings',
|
|
6213
6399
|
dir: path.join('settings', 'forms'),
|
|
@@ -6274,6 +6460,20 @@ const RESOURCE_TYPE_ALIASES = new Map([
|
|
|
6274
6460
|
['form-groups', 'formPermissionGroups'],
|
|
6275
6461
|
['formpermissiongroup', 'formPermissionGroups'],
|
|
6276
6462
|
['formpermissiongroups', 'formPermissionGroups'],
|
|
6463
|
+
['scope-dimension', 'scopeDimensions'],
|
|
6464
|
+
['scope-dimensions', 'scopeDimensions'],
|
|
6465
|
+
['scopedimension', 'scopeDimensions'],
|
|
6466
|
+
['scopedimensions', 'scopeDimensions'],
|
|
6467
|
+
['scope-grant-source', 'scopeGrantSources'],
|
|
6468
|
+
['scope-grant-sources', 'scopeGrantSources'],
|
|
6469
|
+
['scopegrantsource', 'scopeGrantSources'],
|
|
6470
|
+
['scopegrantsources', 'scopeGrantSources'],
|
|
6471
|
+
['data-scope-policy', 'dataScopePolicies'],
|
|
6472
|
+
['data-scope-policies', 'dataScopePolicies'],
|
|
6473
|
+
['scope-policy', 'dataScopePolicies'],
|
|
6474
|
+
['scope-policies', 'dataScopePolicies'],
|
|
6475
|
+
['datascopepolicy', 'dataScopePolicies'],
|
|
6476
|
+
['datascopepolicies', 'dataScopePolicies'],
|
|
6277
6477
|
['form-setting', 'formSettings'],
|
|
6278
6478
|
['form-settings', 'formSettings'],
|
|
6279
6479
|
['settings-form', 'formSettings'],
|
|
@@ -6283,8 +6483,8 @@ const RESOURCE_TYPE_ALIASES = new Map([
|
|
|
6283
6483
|
]);
|
|
6284
6484
|
|
|
6285
6485
|
const RESOURCE_TYPE_ALIAS_GROUPS = new Map([
|
|
6286
|
-
['permission', ['pagePermissionGroups', 'formPermissionGroups']],
|
|
6287
|
-
['permissions', ['pagePermissionGroups', 'formPermissionGroups']],
|
|
6486
|
+
['permission', ['pagePermissionGroups', 'formPermissionGroups', 'scopeDimensions', 'scopeGrantSources', 'dataScopePolicies']],
|
|
6487
|
+
['permissions', ['pagePermissionGroups', 'formPermissionGroups', 'scopeDimensions', 'scopeGrantSources', 'dataScopePolicies']],
|
|
6288
6488
|
]);
|
|
6289
6489
|
|
|
6290
6490
|
function getResourceTypeFilters(positional = [], flags = {}) {
|
|
@@ -6313,7 +6513,7 @@ function getResourceTypeFilters(positional = [], flags = {}) {
|
|
|
6313
6513
|
if (!key) {
|
|
6314
6514
|
fail(
|
|
6315
6515
|
`未知资源类型: ${rawType}。常用类型包括 workflow, notification, route, menu, role, ` +
|
|
6316
|
-
`storage, page-permission-group, form-permission-group, public-access。`
|
|
6516
|
+
`storage, page-permission-group, form-permission-group, scope-policy, public-access。`
|
|
6317
6517
|
);
|
|
6318
6518
|
}
|
|
6319
6519
|
filters.add(key);
|
|
@@ -6523,6 +6723,7 @@ function validateWorkspaceResources(manifest) {
|
|
|
6523
6723
|
}
|
|
6524
6724
|
}
|
|
6525
6725
|
validateNotificationReferences(manifest.notifications || [], errors);
|
|
6726
|
+
validateScopePermissionResources(manifest, errors, warnings);
|
|
6526
6727
|
return {
|
|
6527
6728
|
valid: errors.length === 0,
|
|
6528
6729
|
errors,
|
|
@@ -6610,6 +6811,9 @@ function generateResourceTypes(manifest, outputFile) {
|
|
|
6610
6811
|
const storageConfigCodes = unique((manifest.storageConfigs || []).map(item => item.code).filter(Boolean));
|
|
6611
6812
|
const authConfigCodes = unique((manifest.authConfigs || []).map(item => item.code).filter(Boolean));
|
|
6612
6813
|
const publicAccessPolicyCodes = unique((manifest.publicAccessPolicies || []).map(item => item.code).filter(Boolean));
|
|
6814
|
+
const scopeDimensionCodes = unique((manifest.scopeDimensions || []).map(item => item.code).filter(Boolean));
|
|
6815
|
+
const scopeGrantSourceCodes = unique((manifest.scopeGrantSources || []).map(item => item.code).filter(Boolean));
|
|
6816
|
+
const dataScopePolicyCodes = unique((manifest.dataScopePolicies || []).map(item => item.code).filter(Boolean));
|
|
6613
6817
|
const content = [
|
|
6614
6818
|
'/* eslint-disable */',
|
|
6615
6819
|
'// Generated by openxiangda resource typegen. Do not edit manually.',
|
|
@@ -6638,6 +6842,15 @@ function generateResourceTypes(manifest, outputFile) {
|
|
|
6638
6842
|
`export const publicAccessPolicyCodes = ${JSON.stringify(publicAccessPolicyCodes, null, 2)} as const`,
|
|
6639
6843
|
'export type PublicAccessPolicyCode = typeof publicAccessPolicyCodes[number]',
|
|
6640
6844
|
'',
|
|
6845
|
+
`export const scopeDimensionCodes = ${JSON.stringify(scopeDimensionCodes, null, 2)} as const`,
|
|
6846
|
+
'export type ScopeDimensionCode = typeof scopeDimensionCodes[number]',
|
|
6847
|
+
'',
|
|
6848
|
+
`export const scopeGrantSourceCodes = ${JSON.stringify(scopeGrantSourceCodes, null, 2)} as const`,
|
|
6849
|
+
'export type ScopeGrantSourceCode = typeof scopeGrantSourceCodes[number]',
|
|
6850
|
+
'',
|
|
6851
|
+
`export const dataScopePolicyCodes = ${JSON.stringify(dataScopePolicyCodes, null, 2)} as const`,
|
|
6852
|
+
'export type DataScopePolicyCode = typeof dataScopePolicyCodes[number]',
|
|
6853
|
+
'',
|
|
6641
6854
|
`export const runtimeMenus = ${JSON.stringify(menus, null, 2)} as const`,
|
|
6642
6855
|
'',
|
|
6643
6856
|
`export const runtimeRoutes = ${JSON.stringify(routes, null, 2)} as const`,
|
|
@@ -6657,6 +6870,9 @@ function generateResourceTypes(manifest, outputFile) {
|
|
|
6657
6870
|
functions: functionCodes.length,
|
|
6658
6871
|
authConfigs: authConfigCodes.length,
|
|
6659
6872
|
publicAccessPolicies: publicAccessPolicyCodes.length,
|
|
6873
|
+
scopeDimensions: scopeDimensionCodes.length,
|
|
6874
|
+
scopeGrantSources: scopeGrantSourceCodes.length,
|
|
6875
|
+
dataScopePolicies: dataScopePolicyCodes.length,
|
|
6660
6876
|
};
|
|
6661
6877
|
}
|
|
6662
6878
|
|
|
@@ -6836,10 +7052,61 @@ function validateResourceItem(kind, item, errors, warnings) {
|
|
|
6836
7052
|
errors.push(`${label}: externalRoleCodes 必须是数组`);
|
|
6837
7053
|
}
|
|
6838
7054
|
}
|
|
7055
|
+
if (kind === 'scopeDimensions') {
|
|
7056
|
+
if (!item.name) warnings.push(`${label}: 未声明 name,将使用 code`);
|
|
7057
|
+
if (item.hierarchyMode && !['flat', 'self_parent'].includes(String(item.hierarchyMode))) {
|
|
7058
|
+
errors.push(`${label}: hierarchyMode 只能是 flat 或 self_parent`);
|
|
7059
|
+
}
|
|
7060
|
+
if ((item.sourceFormUuid || item.formUuid) && !item.sourceValueField && !item.valueField) {
|
|
7061
|
+
errors.push(`${label}: 使用表单维护维度时缺少 sourceValueField`);
|
|
7062
|
+
}
|
|
7063
|
+
}
|
|
7064
|
+
if (kind === 'scopeGrantSources') {
|
|
7065
|
+
if (!item.name) warnings.push(`${label}: 未声明 name,将使用 code`);
|
|
7066
|
+
if (!item.sourceFormUuid && !item.formUuid) errors.push(`${label}: 缺少 sourceFormUuid`);
|
|
7067
|
+
const subjectMappings = item.subjectMappings || item.subjects;
|
|
7068
|
+
const dimensionMappings = item.dimensionMappings || item.dimensions;
|
|
7069
|
+
if (!Array.isArray(subjectMappings) || subjectMappings.length === 0) {
|
|
7070
|
+
errors.push(`${label}: 缺少 subjectMappings`);
|
|
7071
|
+
}
|
|
7072
|
+
if (!Array.isArray(dimensionMappings) || dimensionMappings.length === 0) {
|
|
7073
|
+
errors.push(`${label}: 缺少 dimensionMappings`);
|
|
7074
|
+
}
|
|
7075
|
+
}
|
|
7076
|
+
if (kind === 'dataScopePolicies') {
|
|
7077
|
+
if (!item.name) warnings.push(`${label}: 未声明 name,将使用 code`);
|
|
7078
|
+
const rules = item.rules || item.dimensions;
|
|
7079
|
+
if (!Array.isArray(rules) || rules.length === 0) {
|
|
7080
|
+
errors.push(`${label}: 缺少 rules`);
|
|
7081
|
+
} else {
|
|
7082
|
+
rules.forEach((rule, index) => {
|
|
7083
|
+
const ruleLabel = `${label}.rules[${index}]`;
|
|
7084
|
+
const dimensionCode = rule.dimensionCode || rule.dimension;
|
|
7085
|
+
const targetField = rule.field || rule.targetField;
|
|
7086
|
+
if (!dimensionCode) errors.push(`${ruleLabel}: 缺少 dimensionCode`);
|
|
7087
|
+
if (!targetField) errors.push(`${ruleLabel}: 缺少 field/targetField`);
|
|
7088
|
+
if (rule.valuePath && !isSafeScopePolicyValuePath(rule.valuePath)) {
|
|
7089
|
+
errors.push(`${ruleLabel}: valuePath 只能包含字母、数字、下划线、中划线、点或 >`);
|
|
7090
|
+
}
|
|
7091
|
+
if (isScopePolicyMultiValueComponent(rule.componentType)) {
|
|
7092
|
+
warnings.push(`${ruleLabel}: 多值选择/人员/部门字段不适合直接作为 scope_policy 目标字段,建议派生隐藏 scalar key 字段`);
|
|
7093
|
+
} else if (
|
|
7094
|
+
!rule.valuePath &&
|
|
7095
|
+
!rule.componentType &&
|
|
7096
|
+
looksLikeJsonScopeTargetField(targetField)
|
|
7097
|
+
) {
|
|
7098
|
+
warnings.push(`${ruleLabel}: 目标字段看起来可能是选择/人员/部门类字段;若底层是 JSONB,请声明 valuePath:"value" 或 componentType,优先使用隐藏 scalar key`);
|
|
7099
|
+
}
|
|
7100
|
+
});
|
|
7101
|
+
}
|
|
7102
|
+
}
|
|
6839
7103
|
if (kind === 'pagePermissionGroups' && !item.name) errors.push(`${label}: 缺少 name`);
|
|
6840
7104
|
if (kind === 'formPermissionGroups') {
|
|
6841
7105
|
if (!item.name) errors.push(`${label}: 缺少 name`);
|
|
6842
7106
|
if (!item.formCode && !item.formUuid) errors.push(`${label}: 缺少 formCode 或 formUuid`);
|
|
7107
|
+
if (item.dataPermission?.type === 'scope_policy' && !getScopePolicyCode(item.dataPermission)) {
|
|
7108
|
+
errors.push(`${label}: dataPermission.type=scope_policy 时必须声明 policyCode`);
|
|
7109
|
+
}
|
|
6843
7110
|
}
|
|
6844
7111
|
if (kind === 'formSettings') {
|
|
6845
7112
|
if (!item.formCode && !item.formUuid && !item.code) {
|
|
@@ -6856,6 +7123,105 @@ function validateResourceItem(kind, item, errors, warnings) {
|
|
|
6856
7123
|
}
|
|
6857
7124
|
}
|
|
6858
7125
|
|
|
7126
|
+
function validateScopePermissionResources(manifest, errors, warnings) {
|
|
7127
|
+
const policyCodes = new Set(
|
|
7128
|
+
(manifest.dataScopePolicies || [])
|
|
7129
|
+
.map(item => item.code || item.resourceCode)
|
|
7130
|
+
.filter(Boolean)
|
|
7131
|
+
.map(String)
|
|
7132
|
+
);
|
|
7133
|
+
const scopePolicyRefs = [];
|
|
7134
|
+
const inspectPermissionGroup = (kind, item) => {
|
|
7135
|
+
const groups = kind === 'dataViews'
|
|
7136
|
+
? (Array.isArray(item.permissionGroups) ? item.permissionGroups : [])
|
|
7137
|
+
: [item];
|
|
7138
|
+
for (const group of groups) {
|
|
7139
|
+
const dataPermission = group?.dataPermission;
|
|
7140
|
+
if (!dataPermission) continue;
|
|
7141
|
+
if (dataPermission.type === 'scope_policy') {
|
|
7142
|
+
const policyCode = getScopePolicyCode(dataPermission);
|
|
7143
|
+
if (!policyCode) {
|
|
7144
|
+
errors.push(`${resourceLabel(kind, item)}: scope_policy 缺少 policyCode`);
|
|
7145
|
+
continue;
|
|
7146
|
+
}
|
|
7147
|
+
scopePolicyRefs.push(policyCode);
|
|
7148
|
+
if (policyCodes.size > 0 && !policyCodes.has(policyCode)) {
|
|
7149
|
+
warnings.push(`${resourceLabel(kind, item)}: scope_policy 引用 ${policyCode},但 src/resources 中未声明同名 data-scope-policy`);
|
|
7150
|
+
}
|
|
7151
|
+
} else if (containsBusinessScopeLikeField(dataPermission)) {
|
|
7152
|
+
warnings.push(`${resourceLabel(kind, item)}: 检测到疑似业务范围字段条件,业务对象数量超过少量枚举时优先改为 dataPermission.type=scope_policy`);
|
|
7153
|
+
}
|
|
7154
|
+
}
|
|
7155
|
+
};
|
|
7156
|
+
for (const group of manifest.formPermissionGroups || []) {
|
|
7157
|
+
inspectPermissionGroup('formPermissionGroups', group);
|
|
7158
|
+
}
|
|
7159
|
+
for (const view of manifest.dataViews || []) {
|
|
7160
|
+
inspectPermissionGroup('dataViews', view);
|
|
7161
|
+
}
|
|
7162
|
+
const formGroupCount = (manifest.formPermissionGroups || []).length;
|
|
7163
|
+
const pageGroupCount = (manifest.pagePermissionGroups || []).length;
|
|
7164
|
+
if (formGroupCount >= 30) {
|
|
7165
|
+
warnings.push(`formPermissionGroups 当前 ${formGroupCount} 个;如这些组按客户/项目/区域/学院/班级/门店拆分,建议收敛为业务授权表 + scope_policy`);
|
|
7166
|
+
}
|
|
7167
|
+
if (pageGroupCount >= 30) {
|
|
7168
|
+
warnings.push(`pagePermissionGroups 当前 ${pageGroupCount} 个;如这些组按业务对象拆分,建议只保留入口角色,数据范围交给 scope_policy`);
|
|
7169
|
+
}
|
|
7170
|
+
if (scopePolicyRefs.length > 0 && (manifest.scopeGrantSources || []).length === 0) {
|
|
7171
|
+
warnings.push('已引用 scope_policy,但未声明 scopeGrantSources;请确认授权来源由平台后台或其他工作区维护');
|
|
7172
|
+
}
|
|
7173
|
+
}
|
|
7174
|
+
|
|
7175
|
+
function getScopePolicyCode(dataPermission = {}) {
|
|
7176
|
+
return String(
|
|
7177
|
+
dataPermission.policyCode ||
|
|
7178
|
+
dataPermission.scopePolicyCode ||
|
|
7179
|
+
dataPermission.code ||
|
|
7180
|
+
''
|
|
7181
|
+
).trim();
|
|
7182
|
+
}
|
|
7183
|
+
|
|
7184
|
+
function isSafeScopePolicyValuePath(value) {
|
|
7185
|
+
return /^[A-Za-z0-9_.>-]+$/.test(String(value || ''));
|
|
7186
|
+
}
|
|
7187
|
+
|
|
7188
|
+
function isScopePolicyMultiValueComponent(componentType) {
|
|
7189
|
+
const normalized = String(componentType || '').trim().toLowerCase();
|
|
7190
|
+
return [
|
|
7191
|
+
'multiselect',
|
|
7192
|
+
'multiselectfield',
|
|
7193
|
+
'checkbox',
|
|
7194
|
+
'checkboxfield',
|
|
7195
|
+
'cascadeselect',
|
|
7196
|
+
'cascadeselectfield',
|
|
7197
|
+
].includes(normalized);
|
|
7198
|
+
}
|
|
7199
|
+
|
|
7200
|
+
function looksLikeJsonScopeTargetField(field) {
|
|
7201
|
+
return /(select|option|user|employee|member|dept|department|linked|association|人员|用户|部门|选择)/i.test(String(field || ''));
|
|
7202
|
+
}
|
|
7203
|
+
|
|
7204
|
+
function containsBusinessScopeLikeField(value) {
|
|
7205
|
+
const fields = [];
|
|
7206
|
+
const visit = item => {
|
|
7207
|
+
if (!item || typeof item !== 'object') return;
|
|
7208
|
+
if (Array.isArray(item)) {
|
|
7209
|
+
item.forEach(visit);
|
|
7210
|
+
return;
|
|
7211
|
+
}
|
|
7212
|
+
if (item.field || item.key || item.targetField) {
|
|
7213
|
+
fields.push(String(item.field || item.key || item.targetField));
|
|
7214
|
+
}
|
|
7215
|
+
visit(item.condition);
|
|
7216
|
+
visit(item.conditions);
|
|
7217
|
+
visit(item.rules);
|
|
7218
|
+
};
|
|
7219
|
+
visit(value);
|
|
7220
|
+
return fields.some(field =>
|
|
7221
|
+
/(scope|customer|client|project|region|area|store|shop|campus|college|school|class|班级|学院|校区|区域|门店|客户|项目)/i.test(field)
|
|
7222
|
+
);
|
|
7223
|
+
}
|
|
7224
|
+
|
|
6859
7225
|
function validateDataViewPerformance(label, definition, errors, warnings, storageMode = 'materialized') {
|
|
6860
7226
|
const viewType = String(definition.viewType || 'row').toLowerCase();
|
|
6861
7227
|
if (viewType !== 'row' && viewType !== 'aggregate') return;
|
|
@@ -7306,6 +7672,9 @@ async function buildResourcePlan(config, target, manifest) {
|
|
|
7306
7672
|
await addAutomationPlanActions(config, target, actions, manifest.automations, existing.automations);
|
|
7307
7673
|
addPlanActions(actions, 'dataView', manifest.dataViews, existing.dataViews, (item, current) => dataViewEquals(target.bound, item, current));
|
|
7308
7674
|
addPlanActions(actions, 'storageConfig', manifest.storageConfigs, existing.storageConfigs, (item, current) => storageConfigEquals(item, current));
|
|
7675
|
+
addPlanActions(actions, 'scopeDimension', manifest.scopeDimensions, existing.scopeDimensions, (item, current) => scopeDimensionEquals(target.bound, item, current));
|
|
7676
|
+
addPlanActions(actions, 'scopeGrantSource', manifest.scopeGrantSources, existing.scopeGrantSources, (item, current) => scopeGrantSourceEquals(target.bound, item, current));
|
|
7677
|
+
addPlanActions(actions, 'dataScopePolicy', manifest.dataScopePolicies, existing.dataScopePolicies, dataScopePolicyEquals);
|
|
7309
7678
|
addPlanActions(actions, 'pagePermissionGroup', manifest.pagePermissionGroups, existing.pagePermissionGroups, (item, current) => pagePermissionGroupEquals(target.bound, item, current));
|
|
7310
7679
|
addPlanActions(actions, 'formPermissionGroup', manifest.formPermissionGroups, existing.formPermissionGroups, (item, current) => formPermissionGroupEquals(target.bound, item, current));
|
|
7311
7680
|
for (const item of manifest.formSettings || []) {
|
|
@@ -7378,6 +7747,9 @@ async function publishResourceManifest(config, target, manifest, options = {}) {
|
|
|
7378
7747
|
await publishAuthConfigResources(config, target, manifest.authConfigs || [], result, publishOptions);
|
|
7379
7748
|
await publishRouteResources(config, target, manifest.routes || [], result, publishOptions);
|
|
7380
7749
|
await publishAutomationResources(config, target, manifest.automations || [], result, publishOptions);
|
|
7750
|
+
await publishScopeDimensionResources(config, target, manifest.scopeDimensions || [], result, publishOptions);
|
|
7751
|
+
await publishScopeGrantSourceResources(config, target, manifest.scopeGrantSources || [], result, publishOptions);
|
|
7752
|
+
await publishDataScopePolicyResources(config, target, manifest.dataScopePolicies || [], result, publishOptions);
|
|
7381
7753
|
await publishDataViewResources(config, target, manifest.dataViews || [], result, publishOptions);
|
|
7382
7754
|
await publishStorageConfigResources(config, target, manifest.storageConfigs || [], result, publishOptions);
|
|
7383
7755
|
await publishPublicAccessPolicyResources(config, target, manifest.publicAccessPolicies || [], result, publishOptions);
|
|
@@ -7408,6 +7780,9 @@ async function fetchExistingResourceMaps(config, target, manifest) {
|
|
|
7408
7780
|
automations: new Map(),
|
|
7409
7781
|
dataViews: new Map(),
|
|
7410
7782
|
storageConfigs: new Map(),
|
|
7783
|
+
scopeDimensions: new Map(),
|
|
7784
|
+
scopeGrantSources: new Map(),
|
|
7785
|
+
dataScopePolicies: new Map(),
|
|
7411
7786
|
pagePermissionGroups: new Map(),
|
|
7412
7787
|
formPermissionGroups: new Map(),
|
|
7413
7788
|
};
|
|
@@ -7620,6 +7995,30 @@ async function fetchExistingResourceMaps(config, target, manifest) {
|
|
|
7620
7995
|
});
|
|
7621
7996
|
}
|
|
7622
7997
|
}
|
|
7998
|
+
if ((manifest.scopeDimensions || []).length > 0) {
|
|
7999
|
+
const data = await requestWithAuth(
|
|
8000
|
+
config,
|
|
8001
|
+
target.profileName,
|
|
8002
|
+
`/openxiangda-api/v1/apps/${encodeURIComponent(target.appType)}/scope/dimensions`
|
|
8003
|
+
);
|
|
8004
|
+
indexByCode(maps.scopeDimensions, normalizeItems(data), item => item.code || item.resourceCode);
|
|
8005
|
+
}
|
|
8006
|
+
if ((manifest.scopeGrantSources || []).length > 0) {
|
|
8007
|
+
const data = await requestWithAuth(
|
|
8008
|
+
config,
|
|
8009
|
+
target.profileName,
|
|
8010
|
+
`/openxiangda-api/v1/apps/${encodeURIComponent(target.appType)}/scope/grant-sources`
|
|
8011
|
+
);
|
|
8012
|
+
indexByCode(maps.scopeGrantSources, normalizeItems(data), item => item.code || item.resourceCode);
|
|
8013
|
+
}
|
|
8014
|
+
if ((manifest.dataScopePolicies || []).length > 0) {
|
|
8015
|
+
const data = await requestWithAuth(
|
|
8016
|
+
config,
|
|
8017
|
+
target.profileName,
|
|
8018
|
+
`/openxiangda-api/v1/apps/${encodeURIComponent(target.appType)}/scope/policies`
|
|
8019
|
+
);
|
|
8020
|
+
indexByCode(maps.dataScopePolicies, normalizeItems(data), item => item.code || item.resourceCode);
|
|
8021
|
+
}
|
|
7623
8022
|
if ((manifest.pagePermissionGroups || []).length > 0) {
|
|
7624
8023
|
const data = await requestWithAuth(
|
|
7625
8024
|
config,
|
|
@@ -8454,6 +8853,102 @@ async function publishAuthConfigResources(config, target, authConfigs, result, o
|
|
|
8454
8853
|
}
|
|
8455
8854
|
}
|
|
8456
8855
|
|
|
8856
|
+
async function publishScopeDimensionResources(config, target, dimensions, result, options = {}) {
|
|
8857
|
+
for (const item of dimensions) {
|
|
8858
|
+
const code = item.code || item.resourceCode;
|
|
8859
|
+
if (shouldSkipNoopResource(options, 'scopeDimension', code)) {
|
|
8860
|
+
recordNoopResource(result, 'scopeDimension', code, options);
|
|
8861
|
+
continue;
|
|
8862
|
+
}
|
|
8863
|
+
const existing = await findExistingScopeResource(config, target, 'dimensions', code);
|
|
8864
|
+
const body = normalizeScopeDimensionManifest(item, target.bound);
|
|
8865
|
+
const data = await requestWithAuth(
|
|
8866
|
+
config,
|
|
8867
|
+
target.profileName,
|
|
8868
|
+
`/openxiangda-api/v1/apps/${encodeURIComponent(target.appType)}/scope/dimensions/${encodeURIComponent(code)}`,
|
|
8869
|
+
{ method: 'POST', body }
|
|
8870
|
+
);
|
|
8871
|
+
saveScopeResource(target, 'scopeDimensions', code, data);
|
|
8872
|
+
result.published.push({
|
|
8873
|
+
kind: 'scopeDimension',
|
|
8874
|
+
code,
|
|
8875
|
+
action: existing ? 'update' : 'create',
|
|
8876
|
+
id: data?.id,
|
|
8877
|
+
});
|
|
8878
|
+
}
|
|
8879
|
+
}
|
|
8880
|
+
|
|
8881
|
+
async function publishScopeGrantSourceResources(config, target, sources, result, options = {}) {
|
|
8882
|
+
for (const item of sources) {
|
|
8883
|
+
const code = item.code || item.resourceCode;
|
|
8884
|
+
if (shouldSkipNoopResource(options, 'scopeGrantSource', code)) {
|
|
8885
|
+
recordNoopResource(result, 'scopeGrantSource', code, options);
|
|
8886
|
+
continue;
|
|
8887
|
+
}
|
|
8888
|
+
const existing = await findExistingScopeResource(config, target, 'grant-sources', code);
|
|
8889
|
+
const body = normalizeScopeGrantSourceManifest(item, target.bound);
|
|
8890
|
+
const data = await requestWithAuth(
|
|
8891
|
+
config,
|
|
8892
|
+
target.profileName,
|
|
8893
|
+
`/openxiangda-api/v1/apps/${encodeURIComponent(target.appType)}/scope/grant-sources/${encodeURIComponent(code)}`,
|
|
8894
|
+
{ method: 'POST', body }
|
|
8895
|
+
);
|
|
8896
|
+
saveScopeResource(target, 'scopeGrantSources', code, data);
|
|
8897
|
+
const published = {
|
|
8898
|
+
kind: 'scopeGrantSource',
|
|
8899
|
+
code,
|
|
8900
|
+
action: existing ? 'update' : 'create',
|
|
8901
|
+
id: data?.id,
|
|
8902
|
+
};
|
|
8903
|
+
if (item.sync === true) {
|
|
8904
|
+
published.sync = await requestWithAuth(
|
|
8905
|
+
config,
|
|
8906
|
+
target.profileName,
|
|
8907
|
+
`/openxiangda-api/v1/apps/${encodeURIComponent(target.appType)}/scope/grant-sources/${encodeURIComponent(code)}/sync`,
|
|
8908
|
+
{ method: 'POST', body: { dryRun: false } }
|
|
8909
|
+
);
|
|
8910
|
+
}
|
|
8911
|
+
result.published.push(published);
|
|
8912
|
+
}
|
|
8913
|
+
}
|
|
8914
|
+
|
|
8915
|
+
async function publishDataScopePolicyResources(config, target, policies, result, options = {}) {
|
|
8916
|
+
for (const item of policies) {
|
|
8917
|
+
const code = item.code || item.resourceCode;
|
|
8918
|
+
if (shouldSkipNoopResource(options, 'dataScopePolicy', code)) {
|
|
8919
|
+
recordNoopResource(result, 'dataScopePolicy', code, options);
|
|
8920
|
+
continue;
|
|
8921
|
+
}
|
|
8922
|
+
const existing = await findExistingScopeResource(config, target, 'policies', code);
|
|
8923
|
+
const body = normalizeDataScopePolicyManifest(item);
|
|
8924
|
+
const data = await requestWithAuth(
|
|
8925
|
+
config,
|
|
8926
|
+
target.profileName,
|
|
8927
|
+
`/openxiangda-api/v1/apps/${encodeURIComponent(target.appType)}/scope/policies/${encodeURIComponent(code)}`,
|
|
8928
|
+
{ method: 'POST', body }
|
|
8929
|
+
);
|
|
8930
|
+
saveScopeResource(target, 'dataScopePolicies', code, data);
|
|
8931
|
+
result.published.push({
|
|
8932
|
+
kind: 'dataScopePolicy',
|
|
8933
|
+
code,
|
|
8934
|
+
action: existing ? 'update' : 'create',
|
|
8935
|
+
id: data?.id,
|
|
8936
|
+
});
|
|
8937
|
+
}
|
|
8938
|
+
}
|
|
8939
|
+
|
|
8940
|
+
async function findExistingScopeResource(config, target, segment, code) {
|
|
8941
|
+
if (!code) return null;
|
|
8942
|
+
const data = await requestWithAuth(
|
|
8943
|
+
config,
|
|
8944
|
+
target.profileName,
|
|
8945
|
+
`/openxiangda-api/v1/apps/${encodeURIComponent(target.appType)}/scope/${segment}`
|
|
8946
|
+
);
|
|
8947
|
+
return normalizeItems(data).find(item =>
|
|
8948
|
+
String(item.code || item.resourceCode || '') === String(code)
|
|
8949
|
+
);
|
|
8950
|
+
}
|
|
8951
|
+
|
|
8457
8952
|
async function publishDataViewResources(config, target, dataViews, result, options = {}) {
|
|
8458
8953
|
for (const dataViewItem of dataViews) {
|
|
8459
8954
|
if (shouldSkipNoopResource(options, 'dataView', dataViewItem.code)) {
|
|
@@ -10016,6 +10511,60 @@ function normalizePublicAccessPolicyManifest(bound, policy) {
|
|
|
10016
10511
|
});
|
|
10017
10512
|
}
|
|
10018
10513
|
|
|
10514
|
+
function normalizeScopeDimensionManifest(dimension, bound) {
|
|
10515
|
+
const code = dimension.code || dimension.resourceCode;
|
|
10516
|
+
const sourceFormUuid =
|
|
10517
|
+
dimension.sourceFormUuid ||
|
|
10518
|
+
dimension.formUuid ||
|
|
10519
|
+
resolveOptionalFormUuid(bound, dimension.sourceFormCode || dimension.formCode);
|
|
10520
|
+
return stripUndefinedValues({
|
|
10521
|
+
code,
|
|
10522
|
+
name: dimension.name || dimension.title || code,
|
|
10523
|
+
valueType: dimension.valueType || 'string',
|
|
10524
|
+
hierarchyMode: dimension.hierarchyMode || 'flat',
|
|
10525
|
+
sourceFormUuid,
|
|
10526
|
+
sourceValueField: dimension.sourceValueField || dimension.valueField,
|
|
10527
|
+
sourceLabelField: dimension.sourceLabelField || dimension.labelField,
|
|
10528
|
+
sourceParentField: dimension.sourceParentField || dimension.parentField,
|
|
10529
|
+
enabledField: dimension.enabledField,
|
|
10530
|
+
enabled: dimension.enabled !== false,
|
|
10531
|
+
configJson: dimension.configJson || dimension.config,
|
|
10532
|
+
});
|
|
10533
|
+
}
|
|
10534
|
+
|
|
10535
|
+
function normalizeScopeGrantSourceManifest(source, bound) {
|
|
10536
|
+
const code = source.code || source.resourceCode;
|
|
10537
|
+
const sourceFormUuid =
|
|
10538
|
+
source.sourceFormUuid ||
|
|
10539
|
+
source.formUuid ||
|
|
10540
|
+
resolveOptionalFormUuid(bound, source.sourceFormCode || source.formCode);
|
|
10541
|
+
return stripUndefinedValues({
|
|
10542
|
+
code,
|
|
10543
|
+
name: source.name || source.title || code,
|
|
10544
|
+
sourceFormUuid,
|
|
10545
|
+
subjectMappings: source.subjectMappings || source.subjects || [],
|
|
10546
|
+
dimensionMappings: source.dimensionMappings || source.dimensions || [],
|
|
10547
|
+
operationField: source.operationField,
|
|
10548
|
+
enabledField: source.enabledField,
|
|
10549
|
+
effectiveFromField: source.effectiveFromField,
|
|
10550
|
+
effectiveToField: source.effectiveToField,
|
|
10551
|
+
filterJson: source.filterJson || source.filter,
|
|
10552
|
+
enabled: source.enabled !== false,
|
|
10553
|
+
});
|
|
10554
|
+
}
|
|
10555
|
+
|
|
10556
|
+
function normalizeDataScopePolicyManifest(policy) {
|
|
10557
|
+
const code = policy.code || policy.resourceCode;
|
|
10558
|
+
return stripUndefinedValues({
|
|
10559
|
+
code,
|
|
10560
|
+
name: policy.name || policy.title || code,
|
|
10561
|
+
matchMode: policy.matchMode === 'OR' ? 'OR' : 'AND',
|
|
10562
|
+
rules: policy.rules || policy.dimensions || [],
|
|
10563
|
+
configJson: policy.configJson || policy.config,
|
|
10564
|
+
enabled: policy.enabled !== false,
|
|
10565
|
+
});
|
|
10566
|
+
}
|
|
10567
|
+
|
|
10019
10568
|
function normalizePublicAccessGrants(bound, grants = {}) {
|
|
10020
10569
|
const formEntries = [
|
|
10021
10570
|
...normalizePermissionCodeArray(grants.forms),
|
|
@@ -10579,6 +11128,30 @@ function formPermissionGroupEquals(bound, desired, existing) {
|
|
|
10579
11128
|
return stableStringifyForPlan(current, process.cwd()) === stableStringifyForPlan(expected, desired.__dir || process.cwd());
|
|
10580
11129
|
}
|
|
10581
11130
|
|
|
11131
|
+
function scopeDimensionEquals(bound, desired, existing) {
|
|
11132
|
+
if (!existing) return false;
|
|
11133
|
+
const expected = normalizeScopeDimensionManifest(desired, bound);
|
|
11134
|
+
return scopeResourceEquals(expected, existing, desired.__dir);
|
|
11135
|
+
}
|
|
11136
|
+
|
|
11137
|
+
function scopeGrantSourceEquals(bound, desired, existing) {
|
|
11138
|
+
if (!existing) return false;
|
|
11139
|
+
const expected = normalizeScopeGrantSourceManifest(desired, bound);
|
|
11140
|
+
return scopeResourceEquals(expected, existing, desired.__dir);
|
|
11141
|
+
}
|
|
11142
|
+
|
|
11143
|
+
function dataScopePolicyEquals(desired, existing) {
|
|
11144
|
+
if (!existing) return false;
|
|
11145
|
+
const expected = normalizeDataScopePolicyManifest(desired);
|
|
11146
|
+
return scopeResourceEquals(expected, existing, desired.__dir);
|
|
11147
|
+
}
|
|
11148
|
+
|
|
11149
|
+
function scopeResourceEquals(expected, existing, baseDir) {
|
|
11150
|
+
const current = pickObjectKeys(existing, Object.keys(expected));
|
|
11151
|
+
return stableStringifyForPlan(current, process.cwd()) ===
|
|
11152
|
+
stableStringifyForPlan(expected, baseDir || process.cwd());
|
|
11153
|
+
}
|
|
11154
|
+
|
|
10582
11155
|
function normalizeFormPermissionGroupManifest(bound, group) {
|
|
10583
11156
|
const formUuid = resolveManifestFormUuid(bound, group);
|
|
10584
11157
|
const body = {
|