openxiangda 1.0.127 → 1.0.129
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 +1 -1
- package/lib/cli.js +376 -34
- package/package.json +1 -1
- package/packages/sdk/dist/{ProcessPreview-Kexdosgu.d.mts → ProcessPreview-B2-umzSY.d.mts} +12 -6
- package/packages/sdk/dist/{ProcessPreview-Kexdosgu.d.ts → ProcessPreview-B2-umzSY.d.ts} +12 -6
- package/packages/sdk/dist/components/index.cjs +95 -16
- package/packages/sdk/dist/components/index.cjs.map +1 -1
- package/packages/sdk/dist/components/index.d.mts +4 -3
- package/packages/sdk/dist/components/index.d.ts +4 -3
- package/packages/sdk/dist/components/index.mjs +95 -16
- package/packages/sdk/dist/components/index.mjs.map +1 -1
- package/packages/sdk/dist/runtime/index.cjs +131 -16
- package/packages/sdk/dist/runtime/index.cjs.map +1 -1
- package/packages/sdk/dist/runtime/index.d.mts +2 -2
- package/packages/sdk/dist/runtime/index.d.ts +2 -2
- package/packages/sdk/dist/runtime/index.mjs +131 -16
- package/packages/sdk/dist/runtime/index.mjs.map +1 -1
- package/packages/sdk/dist/runtime/react.cjs +34 -0
- package/packages/sdk/dist/runtime/react.cjs.map +1 -1
- package/packages/sdk/dist/runtime/react.d.mts +63 -2
- package/packages/sdk/dist/runtime/react.d.ts +63 -2
- package/packages/sdk/dist/runtime/react.mjs +34 -0
- package/packages/sdk/dist/runtime/react.mjs.map +1 -1
- package/templates/openxiangda-react-spa/src/app/navigation.ts +8 -1
- package/templates/openxiangda-react-spa/src/app/router.tsx +6 -0
- package/templates/openxiangda-react-spa/src/pages/admin/LoginLogPage.tsx +425 -0
- package/templates/openxiangda-react-spa/src/resources/menus/menus.json +9 -0
- package/templates/openxiangda-react-spa/src/resources/permissions/page-groups/app-admin.json +3 -3
- package/templates/openxiangda-react-spa/src/resources/roles/roles.json +6 -0
package/README.md
CHANGED
|
@@ -44,7 +44,7 @@ openxiangda app snapshot APP_XXXX --profile dev --json
|
|
|
44
44
|
|
|
45
45
|
User tokens are stored in `~/.openxiangda/profiles.json` with `0600` permissions. Shared workspace environment values, including `APP_OSS_*`, can live in `~/.openxiangda/.env` and are inherited by new workspaces. Project `.env` files still work and override the global defaults. Project state is stored in `.openxiangda/state.json` and contains only profile-specific resource IDs.
|
|
46
46
|
|
|
47
|
-
React SPA workspaces publish their frontend with `openxiangda runtime deploy`. The default upload mode is staged multipart
|
|
47
|
+
React SPA workspaces publish their frontend with `openxiangda runtime deploy`. The default upload mode is `auto`: it tries staged multipart first, then falls back to built-in OSS direct upload if the staged file API returns HTTP 403. Use `--upload-mode oss-direct` to force built-in OSS direct upload, or `--upload-mode legacy-json` only as a compatibility fallback for older platforms. Upload progress, timeout, and `traceId` are written to stderr; `--json` stdout remains the final JSON result. Use `--upload-timeout-ms <ms>` or `OPENXIANGDA_RUNTIME_UPLOAD_TIMEOUT_MS` to tune runtime upload/finalize timeouts.
|
|
48
48
|
|
|
49
49
|
React SPA templates include stable Vite manual chunk grouping for React, antd, ECharts, editor dependencies, and OpenXiangda runtime/component entrypoints. The SDK keeps large UI dependencies such as `antd-mobile` and `dayjs` external so the application bundler can split them by route. Applications should import from public package entrypoints such as `openxiangda`, `openxiangda/runtime`, and `openxiangda/runtime/react`; do not import `openxiangda/packages/sdk/dist/...` internals to chase bundle size.
|
|
50
50
|
|
package/lib/cli.js
CHANGED
|
@@ -155,7 +155,7 @@ Usage:
|
|
|
155
155
|
openxiangda permission form-group-list|form-group-create|form-group-update|form-group-delete|form-group-bind
|
|
156
156
|
openxiangda settings get|save|indexes|indexes-save|data-management|data-management-save|public-access
|
|
157
157
|
openxiangda resource validate|plan|publish|pull|typegen|explain [--profile name] [--json] [--dry-run]
|
|
158
|
-
openxiangda runtime deploy [--profile name] [--dist dist] [--build-id id] [--upload-mode staged|legacy-json] [--upload-timeout-ms ms] [--no-build] [--no-activate] [--json]
|
|
158
|
+
openxiangda runtime deploy [--profile name] [--dist dist] [--build-id id] [--upload-mode auto|staged|oss-direct|legacy-json] [--upload-timeout-ms ms] [--no-build] [--no-activate] [--json]
|
|
159
159
|
openxiangda runtime releases [--profile name] [--json]
|
|
160
160
|
openxiangda runtime activate <releaseId> [--profile name] [--json]
|
|
161
161
|
openxiangda inspect app|form|workflow|automation|permissions
|
|
@@ -3863,6 +3863,7 @@ async function runtime(args) {
|
|
|
3863
3863
|
' openxiangda runtime releases --profile <name>',
|
|
3864
3864
|
'说明:',
|
|
3865
3865
|
' - deploy 上传当前工作区 dist/ 并激活 React SPA runtime release。',
|
|
3866
|
+
' - 默认 --upload-mode auto;multipart 上传遇到 403 时自动切换到阿里 OSS 直传。',
|
|
3866
3867
|
' - 如果平台服务较旧不支持分片上传,可临时使用 --upload-mode legacy-json。',
|
|
3867
3868
|
' - runtime 只发布前端产物;表单、流程、通知等资源仍走 resource publish。',
|
|
3868
3869
|
].join('\n'));
|
|
@@ -3901,10 +3902,42 @@ async function runtime(args) {
|
|
|
3901
3902
|
if (subcommand === 'deploy') {
|
|
3902
3903
|
const buildId = normalizeRuntimeBuildId(flags['build-id'] || createRuntimeBuildId());
|
|
3903
3904
|
const distDir = path.resolve(process.cwd(), flags.dist || 'dist');
|
|
3904
|
-
const assetBaseUrl = buildRuntimeAssetBaseUrl(target.appType, buildId);
|
|
3905
3905
|
const uploadMode = normalizeRuntimeUploadMode(flags['upload-mode']);
|
|
3906
3906
|
const uploadTimeoutMs = normalizeRuntimeUploadTimeoutMs(flags['upload-timeout-ms']);
|
|
3907
3907
|
const traceId = createRuntimeTraceId();
|
|
3908
|
+
const gatewayAssetBaseUrl = buildRuntimeAssetBaseUrl(target.appType, buildId);
|
|
3909
|
+
let assetBaseUrl = gatewayAssetBaseUrl;
|
|
3910
|
+
let effectiveUploadMode = uploadMode;
|
|
3911
|
+
if (uploadMode === 'auto' || uploadMode === 'oss-direct') {
|
|
3912
|
+
try {
|
|
3913
|
+
const plan = await createRuntimeOssUploadBasePlan({
|
|
3914
|
+
config,
|
|
3915
|
+
profileName: target.profileName,
|
|
3916
|
+
appType: target.appType,
|
|
3917
|
+
buildId,
|
|
3918
|
+
traceId,
|
|
3919
|
+
timeoutMs: uploadTimeoutMs,
|
|
3920
|
+
});
|
|
3921
|
+
if (plan?.assetBaseUrl) {
|
|
3922
|
+
assetBaseUrl = plan.assetBaseUrl;
|
|
3923
|
+
if (uploadMode === 'auto') {
|
|
3924
|
+
effectiveUploadMode = 'oss-direct';
|
|
3925
|
+
printRuntimeProgress(
|
|
3926
|
+
`runtime builtin OSS available; using OSS direct upload traceId=${traceId}`
|
|
3927
|
+
);
|
|
3928
|
+
}
|
|
3929
|
+
}
|
|
3930
|
+
} catch (error) {
|
|
3931
|
+
if (uploadMode === 'oss-direct') {
|
|
3932
|
+
throw error;
|
|
3933
|
+
}
|
|
3934
|
+
effectiveUploadMode = 'auto';
|
|
3935
|
+
assetBaseUrl = gatewayAssetBaseUrl;
|
|
3936
|
+
printRuntimeProgress(
|
|
3937
|
+
`runtime builtin OSS preflight unavailable; using staged upload first traceId=${traceId}`
|
|
3938
|
+
);
|
|
3939
|
+
}
|
|
3940
|
+
}
|
|
3908
3941
|
if (!flags['no-build']) {
|
|
3909
3942
|
runRuntimeBuild({
|
|
3910
3943
|
buildId,
|
|
@@ -3919,26 +3952,58 @@ async function runtime(args) {
|
|
|
3919
3952
|
});
|
|
3920
3953
|
const totalBytes = files.reduce((sum, file) => sum + file.size, 0);
|
|
3921
3954
|
printRuntimeProgress(
|
|
3922
|
-
`runtime release upload: mode=${
|
|
3923
|
-
);
|
|
3924
|
-
|
|
3925
|
-
|
|
3926
|
-
|
|
3927
|
-
|
|
3928
|
-
|
|
3929
|
-
|
|
3930
|
-
|
|
3931
|
-
|
|
3932
|
-
|
|
3933
|
-
|
|
3934
|
-
|
|
3935
|
-
|
|
3936
|
-
|
|
3937
|
-
|
|
3938
|
-
|
|
3939
|
-
|
|
3940
|
-
|
|
3941
|
-
|
|
3955
|
+
`runtime release upload: mode=${effectiveUploadMode} traceId=${traceId} files=${files.length} size=${formatBytes(totalBytes)} timeout=${uploadTimeoutMs}ms`
|
|
3956
|
+
);
|
|
3957
|
+
let releaseFiles;
|
|
3958
|
+
if (uploadMode === 'legacy-json') {
|
|
3959
|
+
releaseFiles = files.map(file => ({
|
|
3960
|
+
path: file.path,
|
|
3961
|
+
size: file.size,
|
|
3962
|
+
sha256: file.sha256,
|
|
3963
|
+
contentType: file.contentType,
|
|
3964
|
+
contentBase64: file.buffer.toString('base64'),
|
|
3965
|
+
}));
|
|
3966
|
+
effectiveUploadMode = 'legacy-json';
|
|
3967
|
+
} else if (effectiveUploadMode === 'oss-direct') {
|
|
3968
|
+
releaseFiles = await uploadRuntimeDistFilesOssDirect({
|
|
3969
|
+
config,
|
|
3970
|
+
profileName: target.profileName,
|
|
3971
|
+
appType: target.appType,
|
|
3972
|
+
buildId,
|
|
3973
|
+
files,
|
|
3974
|
+
traceId,
|
|
3975
|
+
timeoutMs: uploadTimeoutMs,
|
|
3976
|
+
});
|
|
3977
|
+
} else {
|
|
3978
|
+
try {
|
|
3979
|
+
releaseFiles = await uploadRuntimeDistFilesStaged({
|
|
3980
|
+
config,
|
|
3981
|
+
profileName: target.profileName,
|
|
3982
|
+
appType: target.appType,
|
|
3983
|
+
buildId,
|
|
3984
|
+
files,
|
|
3985
|
+
traceId,
|
|
3986
|
+
timeoutMs: uploadTimeoutMs,
|
|
3987
|
+
});
|
|
3988
|
+
} catch (error) {
|
|
3989
|
+
if (uploadMode !== 'auto' || !isRuntimeStagedUploadBlocked(error)) {
|
|
3990
|
+
throw error;
|
|
3991
|
+
}
|
|
3992
|
+
effectiveUploadMode = 'oss-direct';
|
|
3993
|
+
printRuntimeProgress(
|
|
3994
|
+
`runtime staged upload blocked by HTTP 403; fallback to OSS direct upload traceId=${traceId}`
|
|
3995
|
+
);
|
|
3996
|
+
releaseFiles = await uploadRuntimeDistFilesOssDirect({
|
|
3997
|
+
config,
|
|
3998
|
+
profileName: target.profileName,
|
|
3999
|
+
appType: target.appType,
|
|
4000
|
+
buildId,
|
|
4001
|
+
files,
|
|
4002
|
+
traceId,
|
|
4003
|
+
timeoutMs: uploadTimeoutMs,
|
|
4004
|
+
});
|
|
4005
|
+
}
|
|
4006
|
+
}
|
|
3942
4007
|
const data = await requestWithAuth(
|
|
3943
4008
|
config,
|
|
3944
4009
|
target.profileName,
|
|
@@ -3957,6 +4022,7 @@ async function runtime(args) {
|
|
|
3957
4022
|
}
|
|
3958
4023
|
);
|
|
3959
4024
|
saveRuntimeReleaseState(target, data);
|
|
4025
|
+
const releaseAssetBaseUrl = data?.assetBaseUrl || assetBaseUrl;
|
|
3960
4026
|
const result = {
|
|
3961
4027
|
appType: target.appType,
|
|
3962
4028
|
buildId,
|
|
@@ -3964,9 +4030,9 @@ async function runtime(args) {
|
|
|
3964
4030
|
distDir,
|
|
3965
4031
|
fileCount: files.length,
|
|
3966
4032
|
totalBytes,
|
|
3967
|
-
assetBaseUrl,
|
|
4033
|
+
assetBaseUrl: releaseAssetBaseUrl,
|
|
3968
4034
|
activated: !flags['no-activate'],
|
|
3969
|
-
uploadMode,
|
|
4035
|
+
uploadMode: effectiveUploadMode,
|
|
3970
4036
|
traceId,
|
|
3971
4037
|
};
|
|
3972
4038
|
if (flags.json) return writeJson(result);
|
|
@@ -3975,9 +4041,9 @@ async function runtime(args) {
|
|
|
3975
4041
|
`runtime release 已上传: ${target.appType}`,
|
|
3976
4042
|
`build: ${buildId}`,
|
|
3977
4043
|
`files: ${files.length} (${formatBytes(totalBytes)})`,
|
|
3978
|
-
`upload: ${
|
|
4044
|
+
`upload: ${effectiveUploadMode}`,
|
|
3979
4045
|
`traceId: ${traceId}`,
|
|
3980
|
-
`assetBase: ${
|
|
4046
|
+
`assetBase: ${releaseAssetBaseUrl}`,
|
|
3981
4047
|
`active: ${result.activated ? 'yes' : 'no'}`,
|
|
3982
4048
|
].join('\n')
|
|
3983
4049
|
);
|
|
@@ -3987,6 +4053,24 @@ async function runtime(args) {
|
|
|
3987
4053
|
fail('用法: openxiangda runtime deploy|releases|activate [--profile name]');
|
|
3988
4054
|
}
|
|
3989
4055
|
|
|
4056
|
+
async function createRuntimeOssUploadBasePlan(options) {
|
|
4057
|
+
return await requestWithAuth(
|
|
4058
|
+
options.config,
|
|
4059
|
+
options.profileName,
|
|
4060
|
+
`/openxiangda-api/v1/apps/${encodeURIComponent(options.appType)}/runtime/releases/oss-upload-plan`,
|
|
4061
|
+
{
|
|
4062
|
+
method: 'POST',
|
|
4063
|
+
timeoutMs: options.timeoutMs,
|
|
4064
|
+
headers: { 'x-openxiangda-trace-id': options.traceId },
|
|
4065
|
+
body: {
|
|
4066
|
+
buildId: options.buildId,
|
|
4067
|
+
planOnly: true,
|
|
4068
|
+
files: [],
|
|
4069
|
+
},
|
|
4070
|
+
}
|
|
4071
|
+
);
|
|
4072
|
+
}
|
|
4073
|
+
|
|
3990
4074
|
function runRuntimeBuild(options) {
|
|
3991
4075
|
const command = options.command || defaultRuntimeBuildCommand();
|
|
3992
4076
|
if (options.jsonOutput) {
|
|
@@ -4082,6 +4166,95 @@ async function uploadRuntimeDistFilesStaged(options) {
|
|
|
4082
4166
|
}));
|
|
4083
4167
|
}
|
|
4084
4168
|
|
|
4169
|
+
async function uploadRuntimeDistFilesOssDirect(options) {
|
|
4170
|
+
const files = options.files || [];
|
|
4171
|
+
const plan = await requestWithAuth(
|
|
4172
|
+
options.config,
|
|
4173
|
+
options.profileName,
|
|
4174
|
+
`/openxiangda-api/v1/apps/${encodeURIComponent(options.appType)}/runtime/releases/oss-upload-plan`,
|
|
4175
|
+
{
|
|
4176
|
+
method: 'POST',
|
|
4177
|
+
timeoutMs: options.timeoutMs,
|
|
4178
|
+
headers: { 'x-openxiangda-trace-id': options.traceId },
|
|
4179
|
+
body: {
|
|
4180
|
+
buildId: options.buildId,
|
|
4181
|
+
files: files.map(file => ({
|
|
4182
|
+
path: file.path,
|
|
4183
|
+
size: file.size,
|
|
4184
|
+
sha256: file.sha256,
|
|
4185
|
+
contentType: file.contentType,
|
|
4186
|
+
})),
|
|
4187
|
+
},
|
|
4188
|
+
}
|
|
4189
|
+
);
|
|
4190
|
+
const uploadFiles = Array.isArray(plan?.files) ? plan.files : [];
|
|
4191
|
+
const uploadByPath = new Map(uploadFiles.map(file => [file.path, file]));
|
|
4192
|
+
let completed = 0;
|
|
4193
|
+
const results = await runWithConcurrency(
|
|
4194
|
+
files,
|
|
4195
|
+
RUNTIME_UPLOAD_CONCURRENCY,
|
|
4196
|
+
async file => {
|
|
4197
|
+
const uploadInfo = uploadByPath.get(file.path);
|
|
4198
|
+
if (!uploadInfo?.uploadUrl) {
|
|
4199
|
+
fail(`runtime OSS 上传计划缺少文件: ${file.path}`);
|
|
4200
|
+
}
|
|
4201
|
+
const uploaded = await uploadRuntimeDistFileToSignedUrl(options, file, uploadInfo);
|
|
4202
|
+
completed += 1;
|
|
4203
|
+
printRuntimeProgress(
|
|
4204
|
+
`runtime OSS file uploaded [${completed}/${files.length}] ${file.path} ${formatBytes(file.size)} traceId=${options.traceId}`
|
|
4205
|
+
);
|
|
4206
|
+
return uploaded;
|
|
4207
|
+
}
|
|
4208
|
+
);
|
|
4209
|
+
return results.map(file => ({
|
|
4210
|
+
path: file.path,
|
|
4211
|
+
size: file.size,
|
|
4212
|
+
sha256: file.sha256,
|
|
4213
|
+
contentType: file.contentType,
|
|
4214
|
+
storageProvider: 'oss',
|
|
4215
|
+
objectName: file.objectName,
|
|
4216
|
+
bucketName: file.bucketName,
|
|
4217
|
+
}));
|
|
4218
|
+
}
|
|
4219
|
+
|
|
4220
|
+
async function uploadRuntimeDistFileToSignedUrl(options, file, uploadInfo) {
|
|
4221
|
+
const timeoutMs = normalizeRuntimeUploadTimeoutMs(options.timeoutMs);
|
|
4222
|
+
const controller = new AbortController();
|
|
4223
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
4224
|
+
let response;
|
|
4225
|
+
try {
|
|
4226
|
+
response = await fetch(uploadInfo.uploadUrl, {
|
|
4227
|
+
method: String(uploadInfo.uploadMethod || 'PUT').toUpperCase(),
|
|
4228
|
+
headers: uploadInfo.headers || {},
|
|
4229
|
+
body: file.buffer,
|
|
4230
|
+
signal: controller.signal,
|
|
4231
|
+
});
|
|
4232
|
+
} catch (error) {
|
|
4233
|
+
if (isAbortError(error)) {
|
|
4234
|
+
throw new Error(`runtime OSS 文件上传超时: ${file.path}, timeout=${timeoutMs}ms`);
|
|
4235
|
+
}
|
|
4236
|
+
throw new Error(formatFetchError(error, `runtime OSS upload ${file.path}`));
|
|
4237
|
+
} finally {
|
|
4238
|
+
clearTimeout(timer);
|
|
4239
|
+
}
|
|
4240
|
+
if (!response.ok) {
|
|
4241
|
+
const text = await response.text().catch(() => '');
|
|
4242
|
+
throw new Error(
|
|
4243
|
+
`runtime OSS 文件上传失败: ${file.path}, HTTP ${response.status}: ${
|
|
4244
|
+
text || response.statusText || 'request failed'
|
|
4245
|
+
}`
|
|
4246
|
+
);
|
|
4247
|
+
}
|
|
4248
|
+
return {
|
|
4249
|
+
path: file.path,
|
|
4250
|
+
size: file.size,
|
|
4251
|
+
sha256: file.sha256,
|
|
4252
|
+
contentType: file.contentType,
|
|
4253
|
+
objectName: uploadInfo.objectName,
|
|
4254
|
+
bucketName: uploadInfo.bucketName,
|
|
4255
|
+
};
|
|
4256
|
+
}
|
|
4257
|
+
|
|
4085
4258
|
async function uploadRuntimeDistFile(options, file) {
|
|
4086
4259
|
const apiPath = apiPathWithQuery(
|
|
4087
4260
|
`/openxiangda-api/v1/apps/${encodeURIComponent(options.appType)}/runtime/releases/files`,
|
|
@@ -4137,9 +4310,20 @@ async function runWithConcurrency(items, concurrency, worker) {
|
|
|
4137
4310
|
}
|
|
4138
4311
|
|
|
4139
4312
|
function normalizeRuntimeUploadMode(value) {
|
|
4140
|
-
const normalized = String(value || '
|
|
4141
|
-
if (
|
|
4142
|
-
|
|
4313
|
+
const normalized = String(value || 'auto').trim().toLowerCase();
|
|
4314
|
+
if (
|
|
4315
|
+
normalized === 'auto' ||
|
|
4316
|
+
normalized === 'staged' ||
|
|
4317
|
+
normalized === 'oss-direct' ||
|
|
4318
|
+
normalized === 'legacy-json'
|
|
4319
|
+
) {
|
|
4320
|
+
return normalized;
|
|
4321
|
+
}
|
|
4322
|
+
fail(`--upload-mode 只支持 auto、staged、oss-direct 或 legacy-json,当前: ${value}`);
|
|
4323
|
+
}
|
|
4324
|
+
|
|
4325
|
+
function isRuntimeStagedUploadBlocked(error) {
|
|
4326
|
+
return /^HTTP 403\b/.test(String(error?.message || ''));
|
|
4143
4327
|
}
|
|
4144
4328
|
|
|
4145
4329
|
function normalizeRuntimeUploadTimeoutMs(value) {
|
|
@@ -5764,7 +5948,21 @@ function validateResourceItem(kind, item, errors, warnings) {
|
|
|
5764
5948
|
return;
|
|
5765
5949
|
}
|
|
5766
5950
|
|
|
5767
|
-
if (kind === 'roles'
|
|
5951
|
+
if (kind === 'roles') {
|
|
5952
|
+
if (!item.name) errors.push(`${label}: 缺少 name`);
|
|
5953
|
+
if (
|
|
5954
|
+
item.apiPermissionCodes !== undefined &&
|
|
5955
|
+
!Array.isArray(item.apiPermissionCodes)
|
|
5956
|
+
) {
|
|
5957
|
+
errors.push(`${label}: apiPermissionCodes 必须是数组`);
|
|
5958
|
+
}
|
|
5959
|
+
if (
|
|
5960
|
+
item.apiPermissionMode !== undefined &&
|
|
5961
|
+
!['merge', 'replace'].includes(String(item.apiPermissionMode))
|
|
5962
|
+
) {
|
|
5963
|
+
errors.push(`${label}: apiPermissionMode 只能是 merge 或 replace`);
|
|
5964
|
+
}
|
|
5965
|
+
}
|
|
5768
5966
|
if (kind === 'menus' && !item.name) errors.push(`${label}: 缺少 name`);
|
|
5769
5967
|
if (kind === 'workflows') {
|
|
5770
5968
|
if (!item.formCode && !item.formUuid) errors.push(`${label}: 缺少 formCode 或 formUuid`);
|
|
@@ -6421,6 +6619,12 @@ async function fetchExistingResourceMaps(config, target, manifest) {
|
|
|
6421
6619
|
})
|
|
6422
6620
|
);
|
|
6423
6621
|
indexByCode(maps.roles, normalizeItems(data), item => item.code);
|
|
6622
|
+
await hydrateRoleApiPermissions(
|
|
6623
|
+
config,
|
|
6624
|
+
target,
|
|
6625
|
+
maps.roles,
|
|
6626
|
+
manifest.roles || []
|
|
6627
|
+
);
|
|
6424
6628
|
}
|
|
6425
6629
|
if ((manifest.menus || []).length > 0) {
|
|
6426
6630
|
const data = await requestWithAuth(
|
|
@@ -6640,6 +6844,118 @@ async function fetchExistingResourceMaps(config, target, manifest) {
|
|
|
6640
6844
|
return maps;
|
|
6641
6845
|
}
|
|
6642
6846
|
|
|
6847
|
+
function roleDeclaresApiPermissions(role) {
|
|
6848
|
+
if (!Array.isArray(role?.apiPermissionCodes)) return false;
|
|
6849
|
+
if (normalizePermissionCodeArray(role.apiPermissionCodes).length > 0) {
|
|
6850
|
+
return true;
|
|
6851
|
+
}
|
|
6852
|
+
return String(role?.apiPermissionMode || '').trim().toLowerCase() === 'replace';
|
|
6853
|
+
}
|
|
6854
|
+
|
|
6855
|
+
function normalizeApiPermissionMode(role) {
|
|
6856
|
+
const mode = String(role?.apiPermissionMode || 'merge').trim().toLowerCase();
|
|
6857
|
+
if (mode === 'merge' || mode === 'replace') return mode;
|
|
6858
|
+
fail(`${resourceLabel('role', role)}: apiPermissionMode 只能是 merge 或 replace`);
|
|
6859
|
+
}
|
|
6860
|
+
|
|
6861
|
+
function sameStringSet(left, right) {
|
|
6862
|
+
const a = normalizePermissionCodeArray(left);
|
|
6863
|
+
const b = normalizePermissionCodeArray(right);
|
|
6864
|
+
if (a.length !== b.length) return false;
|
|
6865
|
+
const bSet = new Set(b);
|
|
6866
|
+
return a.every(item => bSet.has(item));
|
|
6867
|
+
}
|
|
6868
|
+
|
|
6869
|
+
function isSubsetStringSet(subset, source) {
|
|
6870
|
+
const sourceSet = new Set(normalizePermissionCodeArray(source));
|
|
6871
|
+
return normalizePermissionCodeArray(subset).every(item => sourceSet.has(item));
|
|
6872
|
+
}
|
|
6873
|
+
|
|
6874
|
+
function normalizeApiPermissionRecords(data) {
|
|
6875
|
+
return flattenItems(normalizeItems(data)).filter(item => item?.code);
|
|
6876
|
+
}
|
|
6877
|
+
|
|
6878
|
+
async function fetchRoleApiPermissions(config, target, roleId) {
|
|
6879
|
+
if (!roleId) return [];
|
|
6880
|
+
const data = await requestWithAuth(
|
|
6881
|
+
config,
|
|
6882
|
+
target.profileName,
|
|
6883
|
+
`/permission/api/role/${encodeURIComponent(roleId)}`
|
|
6884
|
+
);
|
|
6885
|
+
return normalizeApiPermissionRecords(data);
|
|
6886
|
+
}
|
|
6887
|
+
|
|
6888
|
+
async function hydrateRoleApiPermissions(config, target, roleMap, desiredRoles) {
|
|
6889
|
+
const roles = (desiredRoles || []).filter(roleDeclaresApiPermissions);
|
|
6890
|
+
if (roles.length === 0) return;
|
|
6891
|
+
for (const role of roles) {
|
|
6892
|
+
const code = role.code || role.resourceCode;
|
|
6893
|
+
const existing = roleMap.get(code);
|
|
6894
|
+
if (!existing?.id) continue;
|
|
6895
|
+
const permissions = await fetchRoleApiPermissions(config, target, existing.id);
|
|
6896
|
+
roleMap.set(code, {
|
|
6897
|
+
...existing,
|
|
6898
|
+
apiPermissionCodes: normalizePermissionCodeArray(
|
|
6899
|
+
permissions.map(item => item.code)
|
|
6900
|
+
),
|
|
6901
|
+
});
|
|
6902
|
+
}
|
|
6903
|
+
}
|
|
6904
|
+
|
|
6905
|
+
async function fetchAppApiPermissionMap(config, target) {
|
|
6906
|
+
const data = await requestWithAuth(
|
|
6907
|
+
config,
|
|
6908
|
+
target.profileName,
|
|
6909
|
+
apiPathWithQuery('/permission/api', {
|
|
6910
|
+
scope: 'app',
|
|
6911
|
+
appType: target.appType,
|
|
6912
|
+
page: 1,
|
|
6913
|
+
limit: 1000,
|
|
6914
|
+
})
|
|
6915
|
+
);
|
|
6916
|
+
const map = new Map();
|
|
6917
|
+
for (const item of normalizeApiPermissionRecords(data)) {
|
|
6918
|
+
map.set(String(item.code), item);
|
|
6919
|
+
}
|
|
6920
|
+
return map;
|
|
6921
|
+
}
|
|
6922
|
+
|
|
6923
|
+
async function syncRoleApiPermissions(config, target, role, roleId) {
|
|
6924
|
+
if (!roleDeclaresApiPermissions(role) || !roleId) return null;
|
|
6925
|
+
const desiredCodes = normalizePermissionCodeArray(role.apiPermissionCodes);
|
|
6926
|
+
const mode = normalizeApiPermissionMode(role);
|
|
6927
|
+
const currentPermissions = await fetchRoleApiPermissions(config, target, roleId);
|
|
6928
|
+
const currentCodes = normalizePermissionCodeArray(
|
|
6929
|
+
currentPermissions.map(item => item.code)
|
|
6930
|
+
);
|
|
6931
|
+
const nextCodes =
|
|
6932
|
+
mode === 'replace' ? desiredCodes : unique([...currentCodes, ...desiredCodes]);
|
|
6933
|
+
if (sameStringSet(currentCodes, nextCodes)) {
|
|
6934
|
+
return { action: 'noop', mode, codes: desiredCodes };
|
|
6935
|
+
}
|
|
6936
|
+
|
|
6937
|
+
const permissionMap = await fetchAppApiPermissionMap(config, target);
|
|
6938
|
+
const currentByCode = new Map(
|
|
6939
|
+
currentPermissions.map(item => [String(item.code), item])
|
|
6940
|
+
);
|
|
6941
|
+
const permissionIds = nextCodes.map(code => {
|
|
6942
|
+
const permission = currentByCode.get(code) || permissionMap.get(code);
|
|
6943
|
+
if (!permission?.id) {
|
|
6944
|
+
fail(
|
|
6945
|
+
`${resourceLabel('role', role)}: 未找到接口权限点 ${code},请确认平台已 seed 该权限`
|
|
6946
|
+
);
|
|
6947
|
+
}
|
|
6948
|
+
return permission.id;
|
|
6949
|
+
});
|
|
6950
|
+
|
|
6951
|
+
await requestWithAuth(config, target.profileName, '/permission/api/assign', {
|
|
6952
|
+
method: 'POST',
|
|
6953
|
+
body: { roleId, permissionIds },
|
|
6954
|
+
});
|
|
6955
|
+
|
|
6956
|
+
return { action: 'update', mode, codes: desiredCodes };
|
|
6957
|
+
}
|
|
6958
|
+
|
|
6643
6959
|
function addPlanActions(actions, kind, desiredItems = [], existingMap, equals) {
|
|
6644
6960
|
for (const item of desiredItems) {
|
|
6645
6961
|
const code = item.code || item.resourceCode || item.methodName;
|
|
@@ -6821,7 +7137,19 @@ async function publishRoleResources(config, target, roles, result, options = {})
|
|
|
6821
7137
|
{ method: 'POST', body: { userIds: role.userIds } }
|
|
6822
7138
|
);
|
|
6823
7139
|
}
|
|
6824
|
-
|
|
7140
|
+
const apiPermissions = await syncRoleApiPermissions(
|
|
7141
|
+
config,
|
|
7142
|
+
target,
|
|
7143
|
+
role,
|
|
7144
|
+
data?.id
|
|
7145
|
+
);
|
|
7146
|
+
result.published.push({
|
|
7147
|
+
kind: 'role',
|
|
7148
|
+
code: role.code,
|
|
7149
|
+
action: existing ? 'update' : 'create',
|
|
7150
|
+
id: data?.id,
|
|
7151
|
+
...(apiPermissions ? { apiPermissions } : {}),
|
|
7152
|
+
});
|
|
6825
7153
|
}
|
|
6826
7154
|
}
|
|
6827
7155
|
|
|
@@ -8124,10 +8452,17 @@ async function pullResources(config, target) {
|
|
|
8124
8452
|
);
|
|
8125
8453
|
for (const role of normalizeItems(roles)) {
|
|
8126
8454
|
const filePath = path.join(baseDir, 'roles', `${role.code}.json`);
|
|
8455
|
+
const apiPermissions = role.id
|
|
8456
|
+
? await fetchRoleApiPermissions(config, target, role.id)
|
|
8457
|
+
: [];
|
|
8458
|
+
const apiPermissionCodes = normalizePermissionCodeArray(
|
|
8459
|
+
apiPermissions.map(item => item.code)
|
|
8460
|
+
);
|
|
8127
8461
|
writeResourceJsonFile(filePath, {
|
|
8128
8462
|
code: role.code,
|
|
8129
8463
|
name: role.name,
|
|
8130
8464
|
description: role.description || '',
|
|
8465
|
+
...(apiPermissionCodes.length > 0 ? { apiPermissionCodes } : {}),
|
|
8131
8466
|
});
|
|
8132
8467
|
written.push(path.relative(process.cwd(), filePath));
|
|
8133
8468
|
}
|
|
@@ -9313,10 +9648,17 @@ function withoutResourceMeta(value) {
|
|
|
9313
9648
|
}
|
|
9314
9649
|
|
|
9315
9650
|
function roleEquals(desired, existing) {
|
|
9316
|
-
|
|
9651
|
+
const metadataEquals =
|
|
9317
9652
|
String(existing.name || '') === String(desired.name || desired.code || '') &&
|
|
9318
|
-
String(existing.description || '') === String(desired.description || '')
|
|
9319
|
-
);
|
|
9653
|
+
String(existing.description || '') === String(desired.description || '');
|
|
9654
|
+
if (!metadataEquals) return false;
|
|
9655
|
+
if (!roleDeclaresApiPermissions(desired)) return true;
|
|
9656
|
+
|
|
9657
|
+
const desiredCodes = normalizePermissionCodeArray(desired.apiPermissionCodes);
|
|
9658
|
+
const existingCodes = normalizePermissionCodeArray(existing.apiPermissionCodes);
|
|
9659
|
+
return normalizeApiPermissionMode(desired) === 'replace'
|
|
9660
|
+
? sameStringSet(desiredCodes, existingCodes)
|
|
9661
|
+
: isSubsetStringSet(desiredCodes, existingCodes);
|
|
9320
9662
|
}
|
|
9321
9663
|
|
|
9322
9664
|
function menuEquals(bound, desired, existing) {
|
package/package.json
CHANGED
|
@@ -252,10 +252,13 @@ interface RuntimeRequestConfig {
|
|
|
252
252
|
headers?: Record<string, string>;
|
|
253
253
|
responseType?: 'json' | 'blob';
|
|
254
254
|
}
|
|
255
|
+
type RuntimeUploadProvider = 'platform' | 'oss' | 'builtin-oss';
|
|
255
256
|
interface RuntimeUploadOptions {
|
|
256
|
-
uploadProvider?:
|
|
257
|
+
uploadProvider?: RuntimeUploadProvider;
|
|
258
|
+
storageScope?: 'app' | 'platform';
|
|
257
259
|
storageCode?: string;
|
|
258
260
|
appType?: string;
|
|
261
|
+
uploadPurpose?: 'attachment' | 'image';
|
|
259
262
|
imageCompression?: ImageCompressionConfig;
|
|
260
263
|
}
|
|
261
264
|
interface FormRuntimeApi {
|
|
@@ -297,6 +300,7 @@ interface FormEngineConfig {
|
|
|
297
300
|
mode: FormEngineMode;
|
|
298
301
|
formUuid: string;
|
|
299
302
|
appType: string;
|
|
303
|
+
defaultUploadProvider?: RuntimeUploadProvider;
|
|
300
304
|
formInstanceId?: string;
|
|
301
305
|
submitBehavior?: FormSubmitBehavior;
|
|
302
306
|
permissions?: {
|
|
@@ -592,6 +596,8 @@ interface AttachmentItem {
|
|
|
592
596
|
uid?: string;
|
|
593
597
|
status?: 'uploading' | 'done' | 'error';
|
|
594
598
|
provider?: 'platform' | 'oss';
|
|
599
|
+
uploadProvider?: RuntimeUploadProvider;
|
|
600
|
+
storageScope?: 'app' | 'platform';
|
|
595
601
|
storageCode?: string;
|
|
596
602
|
appType?: string;
|
|
597
603
|
objectName?: string;
|
|
@@ -642,8 +648,8 @@ interface AttachmentFieldProps extends BaseFieldProps {
|
|
|
642
648
|
maxSize?: number;
|
|
643
649
|
uploadAction?: string;
|
|
644
650
|
bucketName?: string;
|
|
645
|
-
/**
|
|
646
|
-
uploadProvider?:
|
|
651
|
+
/** 自定义上传提供方;oss 需配置 storageCode,builtin-oss 使用平台内置 OSS。 */
|
|
652
|
+
uploadProvider?: RuntimeUploadProvider;
|
|
647
653
|
/** src/resources/storage/<code>.json 中声明的存储 code。 */
|
|
648
654
|
storageCode?: string;
|
|
649
655
|
multiple?: boolean;
|
|
@@ -664,8 +670,8 @@ interface ImageFieldProps extends BaseFieldProps {
|
|
|
664
670
|
accept?: string;
|
|
665
671
|
uploadAction?: string;
|
|
666
672
|
bucketName?: string;
|
|
667
|
-
/**
|
|
668
|
-
uploadProvider?:
|
|
673
|
+
/** 自定义上传提供方;oss 需配置 storageCode,builtin-oss 使用平台内置 OSS。 */
|
|
674
|
+
uploadProvider?: RuntimeUploadProvider;
|
|
669
675
|
/** src/resources/storage/<code>.json 中声明的存储 code。 */
|
|
670
676
|
storageCode?: string;
|
|
671
677
|
multiple?: boolean;
|
|
@@ -1156,4 +1162,4 @@ interface ProcessPreviewProps {
|
|
|
1156
1162
|
}
|
|
1157
1163
|
declare const ProcessPreview: React__default.FC<ProcessPreviewProps>;
|
|
1158
1164
|
|
|
1159
|
-
export { type FormRuntimeApiConfig as $, type AddressFieldProps as A, type BaseFieldProps as B, type CascadeDateFieldProps as C, type DataFilter as D, type DepartmentSelectFieldProps as E, type DepartmentTreeNode as F, type DigitalSignatureFieldProps as G, type DigitalSignatureValue as H, type EditorChoiceOption as I, type EditorFieldProps as J, type EditorToolbarAction as K, type FieldBehavior as L, type FieldDefinition as M, type FieldLayoutNode as N, type FieldValueSyncConfig as O, type FormAppearanceConfig as P, type FormDataDeleteParams as Q, type FormDataQueryParams as R, type FormEffect as S, type FormEffectAction as T, type FormEffectCondition as U, type FormEffectConditionOperator as V, type FormEngineConfig as W, type FormEngineMode as X, type FormInstanceData as Y, type FormLayoutNode as Z, type FormRuntimeApi as _, type AddressValue as a, type
|
|
1165
|
+
export { type FormRuntimeApiConfig as $, type AddressFieldProps as A, type BaseFieldProps as B, type CascadeDateFieldProps as C, type DataFilter as D, type DepartmentSelectFieldProps as E, type DepartmentTreeNode as F, type DigitalSignatureFieldProps as G, type DigitalSignatureValue as H, type EditorChoiceOption as I, type EditorFieldProps as J, type EditorToolbarAction as K, type FieldBehavior as L, type FieldDefinition as M, type FieldLayoutNode as N, type FieldValueSyncConfig as O, type FormAppearanceConfig as P, type FormDataDeleteParams as Q, type FormDataQueryParams as R, type FormEffect as S, type FormEffectAction as T, type FormEffectCondition as U, type FormEffectConditionOperator as V, type FormEngineConfig as W, type FormEngineMode as X, type FormInstanceData as Y, type FormLayoutNode as Z, type FormRuntimeApi as _, type AddressValue as a, type SubFormColumn as a$, type FormRuntimeConfig as a0, type FormSchema as a1, FormSection as a2, type FormSectionProps as a3, type FormSubmitBehavior as a4, type FormTemplateConfig as a5, type GridLayoutCell as a6, type GridLayoutNode as a7, type ImageCompressionConfig as a8, type ImageCompressionVariantConfig as a9, type ProcessNodeType as aA, ProcessPreview as aB, type ProcessPreviewProps as aC, type ProcessRoute as aD, type ProcessStatus as aE, type ProcessTask as aF, type RadioFieldProps as aG, type ResubmitParams as aH, type ReturnParams as aI, type ReturnPolicy as aJ, type ReturnableNode as aK, type ReturnableNodeResult as aL, type RuntimeDataQueryParams as aM, type RuntimeDataQueryResult as aN, type RuntimeRequestConfig as aO, type RuntimeResponse as aP, type RuntimeUploadOptions as aQ, type RuntimeUploadProvider as aR, type SaveTaskParams as aS, type SectionLayoutNode as aT, type SelectFieldProps as aU, type SerialNumberFieldProps as aV, type SignaturePoint as aW, type StandardFormPageMode as aX, type StatusMeta as aY, type StepLayoutItem as aZ, type StepsLayoutNode as a_, type ImageFieldProps as aa, type ImageVariant as ab, type InitiatorSelectCandidate as ac, type InitiatorSelectRequirement as ad, type InitiatorSelectScope as ae, type InitiatorSelectedApprovers as af, type JSONFieldProps as ag, type LayoutVisibleWhen as ah, type LinkedFormOptionConfig as ai, type LocationFieldProps as aj, type LocationValue as ak, type LowcodePageMeta as al, type LowcodePageNode as am, type LowcodePageNodeType as an, type LowcodePageSchema as ao, type MultiSelectFieldProps as ap, type NumberFieldProps as aq, type OptionItem as ar, type OptionSourceConfig as as, type OptionSourceType as at, type PeopleShortcutConfig as au, type PeopleShortcutType as av, type PreviewParams as aw, type ProcessAction as ax, type ProcessBasicInfo as ay, type ProcessDefinition as az, type ApprovalActionType as b, type SubFormFieldProps as b0, type TabLayoutItem as b1, type TabsLayoutNode as b2, type TaskStatus as b3, type TextAreaFieldProps as b4, type TextFieldProps as b5, type TextShortcutConfig as b6, type TextShortcutType as b7, type TransferParams as b8, type UserDisplayFormat as b9, type UserItem as ba, type UserSelectFieldProps as bb, type ValidationPreset as bc, type ValidationRule as bd, type ViewPermissionQueryParams as be, type ViewPermissionSummary as bf, type WithdrawParams as bg, type ApprovalPermission as c, ApprovalTimeline as d, type ApprovalTimelineProps as e, type ApproveParams as f, type AssociationFormConfig as g, type AssociationFormFieldProps as h, type AssociationValue as i, type AttachmentFieldProps as j, type AttachmentImageVariants as k, type AttachmentItem as l, type BaseLayoutNode as m, type CascadeSelectFieldProps as n, type ChangeRecord as o, type ChangeRecordListResponse as p, type ChangeRecordQueryParams as q, type CheckboxFieldProps as r, type DataLinkageCondition as s, type DataLinkageConfig as t, type DateFieldProps as u, type DateRangeRestriction as v, type DateRestrictionConfig as w, type DateShortcutConfig as x, type DateShortcutType as y, type DefaultValueLinkageConfig as z };
|