openxiangda 1.0.128 → 1.0.130

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
@@ -108,6 +108,7 @@ openxiangda auth-config methods --json
108
108
  openxiangda function invoke submit_public_registration --body-json '{"input":{}}'
109
109
  openxiangda connector invoke sms.sendCode --body-json '{"body":{"phone":"13800000000"}}'
110
110
  openxiangda notification preview public_register_notice --body-json '{"payload":{"title":"测试"}}'
111
+ openxiangda permission snapshot --form-codes customer,orders --json
111
112
  openxiangda permission audit --json
112
113
  ```
113
114
 
package/lib/cli.js CHANGED
@@ -150,12 +150,12 @@ Usage:
150
150
  openxiangda function list|get|create|update|upsert|delete|invoke [--json-file file]
151
151
  openxiangda connector list|get|create|update|upsert|delete|invoke|download-test [--json-file file]
152
152
  openxiangda notification template-list|template-get|template-upsert|template-delete|type-list|type-get|type-upsert|type-delete|preview|send|batch-send
153
- openxiangda permission role-list|role-create|role-update|role-delete|role-bind|audit
153
+ openxiangda permission role-list|role-create|role-update|role-delete|role-bind|snapshot|audit
154
154
  openxiangda permission page-group-list|page-group-create|page-group-update|page-group-delete|page-group-bind
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
@@ -3140,7 +3140,7 @@ async function permission(args) {
3140
3140
  const { flags, positional } = parseArgs(rest);
3141
3141
  if (wantsSubcommandHelp(subcommand, flags)) {
3142
3142
  print(
3143
- '用法: 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|audit'
3143
+ '用法: 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'
3144
3144
  );
3145
3145
  return;
3146
3146
  }
@@ -3579,6 +3579,36 @@ async function permission(args) {
3579
3579
  return;
3580
3580
  }
3581
3581
 
3582
+ if (subcommand === 'snapshot') {
3583
+ const target = getWorkspaceTarget(config, profileName, flags);
3584
+ const formUuids = resolvePermissionSnapshotFormUuids(target.bound, flags);
3585
+ const data = await requestWithAuth(
3586
+ config,
3587
+ target.profileName,
3588
+ `/openxiangda-api/v1/apps/${encodeURIComponent(target.appType)}/permissions/snapshot`,
3589
+ {
3590
+ method: 'POST',
3591
+ body: {
3592
+ formUuids,
3593
+ includeRoles: flags['no-roles'] ? false : undefined,
3594
+ includePageGroups: flags['no-page-groups'] ? false : undefined,
3595
+ includeFormGroups: flags['no-form-groups'] ? false : undefined,
3596
+ },
3597
+ }
3598
+ );
3599
+ if (flags.json) return writeJson(data);
3600
+ const formGroups = data.formPermissionGroups || {};
3601
+ print(
3602
+ [
3603
+ `Permission snapshot appType=${data.appType || target.appType}`,
3604
+ `roles=${Array.isArray(data.roles) ? data.roles.length : 0}`,
3605
+ `pageGroups=${Array.isArray(data.pagePermissionGroups) ? data.pagePermissionGroups.length : 0}`,
3606
+ `formGroups=${Number(formGroups.total || 0)}`,
3607
+ ].join(' ')
3608
+ );
3609
+ return;
3610
+ }
3611
+
3582
3612
  if (subcommand === 'audit') {
3583
3613
  const target = getWorkspaceTarget(config, profileName, flags);
3584
3614
  const result = await buildPermissionAudit(config, target, flags);
@@ -3589,7 +3619,7 @@ async function permission(args) {
3589
3619
  }
3590
3620
 
3591
3621
  fail(
3592
- '用法: 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|audit'
3622
+ '用法: 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'
3593
3623
  );
3594
3624
  }
3595
3625
 
@@ -3902,10 +3932,42 @@ async function runtime(args) {
3902
3932
  if (subcommand === 'deploy') {
3903
3933
  const buildId = normalizeRuntimeBuildId(flags['build-id'] || createRuntimeBuildId());
3904
3934
  const distDir = path.resolve(process.cwd(), flags.dist || 'dist');
3905
- const assetBaseUrl = buildRuntimeAssetBaseUrl(target.appType, buildId);
3906
3935
  const uploadMode = normalizeRuntimeUploadMode(flags['upload-mode']);
3907
3936
  const uploadTimeoutMs = normalizeRuntimeUploadTimeoutMs(flags['upload-timeout-ms']);
3908
3937
  const traceId = createRuntimeTraceId();
3938
+ const gatewayAssetBaseUrl = buildRuntimeAssetBaseUrl(target.appType, buildId);
3939
+ let assetBaseUrl = gatewayAssetBaseUrl;
3940
+ let effectiveUploadMode = uploadMode;
3941
+ if (uploadMode === 'auto' || uploadMode === 'oss-direct') {
3942
+ try {
3943
+ const plan = await createRuntimeOssUploadBasePlan({
3944
+ config,
3945
+ profileName: target.profileName,
3946
+ appType: target.appType,
3947
+ buildId,
3948
+ traceId,
3949
+ timeoutMs: uploadTimeoutMs,
3950
+ });
3951
+ if (plan?.assetBaseUrl) {
3952
+ assetBaseUrl = plan.assetBaseUrl;
3953
+ if (uploadMode === 'auto') {
3954
+ effectiveUploadMode = 'oss-direct';
3955
+ printRuntimeProgress(
3956
+ `runtime builtin OSS available; using OSS direct upload traceId=${traceId}`
3957
+ );
3958
+ }
3959
+ }
3960
+ } catch (error) {
3961
+ if (uploadMode === 'oss-direct') {
3962
+ throw error;
3963
+ }
3964
+ effectiveUploadMode = 'auto';
3965
+ assetBaseUrl = gatewayAssetBaseUrl;
3966
+ printRuntimeProgress(
3967
+ `runtime builtin OSS preflight unavailable; using staged upload first traceId=${traceId}`
3968
+ );
3969
+ }
3970
+ }
3909
3971
  if (!flags['no-build']) {
3910
3972
  runRuntimeBuild({
3911
3973
  buildId,
@@ -3920,9 +3982,8 @@ async function runtime(args) {
3920
3982
  });
3921
3983
  const totalBytes = files.reduce((sum, file) => sum + file.size, 0);
3922
3984
  printRuntimeProgress(
3923
- `runtime release upload: mode=${uploadMode} traceId=${traceId} files=${files.length} size=${formatBytes(totalBytes)} timeout=${uploadTimeoutMs}ms`
3985
+ `runtime release upload: mode=${effectiveUploadMode} traceId=${traceId} files=${files.length} size=${formatBytes(totalBytes)} timeout=${uploadTimeoutMs}ms`
3924
3986
  );
3925
- let effectiveUploadMode = uploadMode;
3926
3987
  let releaseFiles;
3927
3988
  if (uploadMode === 'legacy-json') {
3928
3989
  releaseFiles = files.map(file => ({
@@ -3932,7 +3993,8 @@ async function runtime(args) {
3932
3993
  contentType: file.contentType,
3933
3994
  contentBase64: file.buffer.toString('base64'),
3934
3995
  }));
3935
- } else if (uploadMode === 'oss-direct') {
3996
+ effectiveUploadMode = 'legacy-json';
3997
+ } else if (effectiveUploadMode === 'oss-direct') {
3936
3998
  releaseFiles = await uploadRuntimeDistFilesOssDirect({
3937
3999
  config,
3938
4000
  profileName: target.profileName,
@@ -3990,6 +4052,7 @@ async function runtime(args) {
3990
4052
  }
3991
4053
  );
3992
4054
  saveRuntimeReleaseState(target, data);
4055
+ const releaseAssetBaseUrl = data?.assetBaseUrl || assetBaseUrl;
3993
4056
  const result = {
3994
4057
  appType: target.appType,
3995
4058
  buildId,
@@ -3997,7 +4060,7 @@ async function runtime(args) {
3997
4060
  distDir,
3998
4061
  fileCount: files.length,
3999
4062
  totalBytes,
4000
- assetBaseUrl,
4063
+ assetBaseUrl: releaseAssetBaseUrl,
4001
4064
  activated: !flags['no-activate'],
4002
4065
  uploadMode: effectiveUploadMode,
4003
4066
  traceId,
@@ -4010,7 +4073,7 @@ async function runtime(args) {
4010
4073
  `files: ${files.length} (${formatBytes(totalBytes)})`,
4011
4074
  `upload: ${effectiveUploadMode}`,
4012
4075
  `traceId: ${traceId}`,
4013
- `assetBase: ${assetBaseUrl}`,
4076
+ `assetBase: ${releaseAssetBaseUrl}`,
4014
4077
  `active: ${result.activated ? 'yes' : 'no'}`,
4015
4078
  ].join('\n')
4016
4079
  );
@@ -4020,6 +4083,24 @@ async function runtime(args) {
4020
4083
  fail('用法: openxiangda runtime deploy|releases|activate [--profile name]');
4021
4084
  }
4022
4085
 
4086
+ async function createRuntimeOssUploadBasePlan(options) {
4087
+ return await requestWithAuth(
4088
+ options.config,
4089
+ options.profileName,
4090
+ `/openxiangda-api/v1/apps/${encodeURIComponent(options.appType)}/runtime/releases/oss-upload-plan`,
4091
+ {
4092
+ method: 'POST',
4093
+ timeoutMs: options.timeoutMs,
4094
+ headers: { 'x-openxiangda-trace-id': options.traceId },
4095
+ body: {
4096
+ buildId: options.buildId,
4097
+ planOnly: true,
4098
+ files: [],
4099
+ },
4100
+ }
4101
+ );
4102
+ }
4103
+
4023
4104
  function runRuntimeBuild(options) {
4024
4105
  const command = options.command || defaultRuntimeBuildCommand();
4025
4106
  if (options.jsonOutput) {
@@ -4475,7 +4556,7 @@ async function commands(args) {
4475
4556
  'function list|get|create|update|upsert|delete|invoke',
4476
4557
  'connector list|get|create|update|upsert|delete|invoke|download-test',
4477
4558
  'notification template-list|template-get|template-upsert|template-delete|type-list|type-get|type-upsert|type-delete|preview|send|batch-send',
4478
- 'permission role-list|role-create|role-update|role-delete|role-bind|role-users|role-add-users|audit',
4559
+ 'permission role-list|role-create|role-update|role-delete|role-bind|role-users|role-add-users|snapshot|audit',
4479
4560
  'permission page-group-list|page-group-create|page-group-update|page-group-delete|page-group-bind',
4480
4561
  'permission form-group-list|form-group-create|form-group-update|form-group-delete|form-group-bind|form-summary|menu-permissions',
4481
4562
  'settings get|save|indexes|indexes-save|data-management|data-management-save|public-access|public-access-save|public-access-delete',
@@ -5024,10 +5105,21 @@ async function buildPermissionAudit(config, target, flags = {}) {
5024
5105
  warnings,
5025
5106
  };
5026
5107
  if (flags.live) {
5027
- result.live = {
5028
- roles: await requestWithAuth(config, target.profileName, `/openxiangda-api/v1/apps/${encodeURIComponent(target.appType)}/roles`),
5029
- pagePermissionGroups: await requestWithAuth(config, target.profileName, `/openxiangda-api/v1/apps/${encodeURIComponent(target.appType)}/page-permission-groups`),
5030
- };
5108
+ result.live = await requestWithAuth(
5109
+ config,
5110
+ target.profileName,
5111
+ `/openxiangda-api/v1/apps/${encodeURIComponent(target.appType)}/permissions/snapshot`,
5112
+ {
5113
+ method: 'POST',
5114
+ body: {
5115
+ formUuids: unique(
5116
+ (manifest.formPermissionGroups || [])
5117
+ .map(group => resolveManifestFormUuid(target.bound, group))
5118
+ .filter(Boolean)
5119
+ ),
5120
+ },
5121
+ }
5122
+ );
5031
5123
  }
5032
5124
  return result;
5033
5125
  }
@@ -5066,6 +5158,25 @@ function resolveOptionalFormUuid(bound, formCode) {
5066
5158
  return bound.resources?.forms?.[formCode]?.formUuid || formCode;
5067
5159
  }
5068
5160
 
5161
+ function resolvePermissionSnapshotFormUuids(bound, flags = {}) {
5162
+ const directFormUuids = [
5163
+ ...splitList(flags['form-uuids']),
5164
+ ...splitList(flags['form-uuid']),
5165
+ ];
5166
+ const mappedFormUuids = [
5167
+ ...splitList(flags['form-codes']),
5168
+ ...splitList(flags['form-code']),
5169
+ ]
5170
+ .map(formCode => resolveOptionalFormUuid(bound, formCode))
5171
+ .filter(Boolean);
5172
+ const allFormUuids = flags['all-forms']
5173
+ ? Object.values(bound.resources?.forms || {})
5174
+ .map(item => item?.formUuid)
5175
+ .filter(Boolean)
5176
+ : [];
5177
+ return unique([...directFormUuids, ...mappedFormUuids, ...allFormUuids]);
5178
+ }
5179
+
5069
5180
  function resolveOptionalPageId(bound, pageCode) {
5070
5181
  if (!pageCode) return undefined;
5071
5182
  return bound.resources?.pages?.[pageCode]?.pageId || pageCode;
@@ -127,8 +127,14 @@ for real backend read/write field access (`edit`, `readonly`, `hidden`) with
127
127
  ```bash
128
128
  openxiangda permission form-summary --form-code customer --profile dev --json
129
129
  openxiangda permission menu-permissions --profile dev --json
130
+ openxiangda permission snapshot --profile dev --form-codes customer,orders --json
130
131
  ```
131
132
 
133
+ When auditing many roles or permission groups, prefer `openxiangda permission
134
+ snapshot` over looping `role-list`, `page-group-list`, or `form-group-list`.
135
+ The snapshot command uses the platform batch endpoint and returns roles, page
136
+ permission groups, and form permission groups in one request.
137
+
132
138
  ## Form Settings
133
139
 
134
140
  Use settings commands for form runtime settings, field indexes, data management pages, and public access:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openxiangda",
3
- "version": "1.0.128",
3
+ "version": "1.0.130",
4
4
  "description": "OpenXiangda CLI, workspace build tools, runtime SDK, and form components.",
5
5
  "private": false,
6
6
  "bin": {
@@ -67,6 +67,7 @@
67
67
  "test:resource-plan": "node scripts/resource-plan-smoke.mjs",
68
68
  "test:design-gates": "node scripts/design-gates-smoke.mjs",
69
69
  "test:resource-cli": "node scripts/resource-cli-smoke.mjs",
70
+ "test:permission-snapshot": "node scripts/permission-snapshot-smoke.mjs",
70
71
  "test:sdk-bundle": "node scripts/sdk-browser-bundle-smoke.mjs",
71
72
  "test:api-contract": "node scripts/openxiangda-api-contract-smoke.mjs",
72
73
  "test:help-no-side-effects": "node scripts/help-no-side-effects-smoke.mjs",