openxiangda 1.0.92 → 1.0.94
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 +42 -0
- package/lib/cli.js +1211 -23
- package/lib/design-gates.js +449 -0
- package/openxiangda-skills/SKILL.md +23 -20
- package/openxiangda-skills/references/architecture-design.md +25 -0
- package/openxiangda-skills/references/resource-manifest-cheatsheet.md +36 -0
- package/openxiangda-skills/skills/openxiangda-architecture-design/SKILL.md +29 -0
- package/package.json +4 -1
- package/templates/openxiangda-react-spa/AGENTS.md +6 -0
- package/templates/sy-lowcode-app-workspace/AGENTS.md +7 -0
package/lib/cli.js
CHANGED
|
@@ -22,6 +22,13 @@ const { requestJson } = require('./http');
|
|
|
22
22
|
const { getSkillStatusReport, installSkills } = require('./skills');
|
|
23
23
|
const { assertCanInitializeWorkspace, initWorkspace } = require('./workspace-init');
|
|
24
24
|
const { bootstrapWorkspace } = require('./workspace-bootstrap');
|
|
25
|
+
const {
|
|
26
|
+
getDesignGates,
|
|
27
|
+
getResourceExplain,
|
|
28
|
+
renderDesignGatesText,
|
|
29
|
+
renderDesignTemplate,
|
|
30
|
+
renderResourceExplain,
|
|
31
|
+
} = require('./design-gates');
|
|
25
32
|
const {
|
|
26
33
|
fail,
|
|
27
34
|
formatFetchError,
|
|
@@ -52,6 +59,8 @@ async function main(argv) {
|
|
|
52
59
|
if (command === 'env') return env(rest);
|
|
53
60
|
if (command === 'auth') return auth(rest);
|
|
54
61
|
if (command === 'platform') return platform(rest);
|
|
62
|
+
if (command === 'doctor') return doctor(rest);
|
|
63
|
+
if (command === 'design') return design(rest);
|
|
55
64
|
if (command === 'workspace') return workspace(rest);
|
|
56
65
|
if (command === 'app') return app(rest);
|
|
57
66
|
if (command === 'form') return form(rest);
|
|
@@ -60,6 +69,12 @@ async function main(argv) {
|
|
|
60
69
|
if (command === 'workflow') return workflow(rest);
|
|
61
70
|
if (command === 'automation') return automation(rest);
|
|
62
71
|
if (command === 'data-view') return dataView(rest);
|
|
72
|
+
if (command === 'route') return route(rest);
|
|
73
|
+
if (command === 'public-access') return publicAccess(rest);
|
|
74
|
+
if (command === 'auth-config') return authConfig(rest);
|
|
75
|
+
if (command === 'function') return appFunction(rest);
|
|
76
|
+
if (command === 'connector') return connector(rest);
|
|
77
|
+
if (command === 'notification') return notification(rest);
|
|
63
78
|
if (command === 'permission') return permission(rest);
|
|
64
79
|
if (command === 'settings') return settings(rest);
|
|
65
80
|
if (command === 'resource') return resource(rest);
|
|
@@ -82,6 +97,8 @@ Usage:
|
|
|
82
97
|
openxiangda platform list
|
|
83
98
|
openxiangda platform use <name>
|
|
84
99
|
openxiangda auth status|refresh|logout [--profile name]
|
|
100
|
+
openxiangda doctor [--profile name] [--app-type APP_XXX] [--json]
|
|
101
|
+
openxiangda design gates|template [--topic code] [--json]
|
|
85
102
|
openxiangda env [--profile name]
|
|
86
103
|
openxiangda workspace init [dir] [--name package-name] [--runtime legacy|react-spa] [--install] [--profile name --app-type APP_XXX]
|
|
87
104
|
openxiangda workspace init [dir] --profile <name> --app-name <app-name> [--runtime legacy|react-spa] [--install]
|
|
@@ -100,6 +117,7 @@ Usage:
|
|
|
100
117
|
openxiangda page bind <pageCode> --page-id <id>
|
|
101
118
|
openxiangda menu list [--profile name] [--json]
|
|
102
119
|
openxiangda menu create <menuCode> --name <text> --type <nav|receipt|display>
|
|
120
|
+
openxiangda menu update|sort [--json-file file] [--write-manifest]
|
|
103
121
|
openxiangda menu bind <menuCode> --menu-id <id>
|
|
104
122
|
openxiangda workflow list [--profile name] [--form-code code] [--json]
|
|
105
123
|
openxiangda workflow create <workflowCode> --form-code <formCode> --definition-json <file>
|
|
@@ -112,12 +130,18 @@ Usage:
|
|
|
112
130
|
openxiangda automation logs <instanceId> [--automation <automationCode|automationId>] [--summary] [--redact] [--json]
|
|
113
131
|
openxiangda automation diagnose <automationCode|automationId> [--redact] [--json]
|
|
114
132
|
openxiangda automation publish|enable|disable <automationCode|automationId>
|
|
115
|
-
openxiangda data-view list|status|refresh|query|stats <dataViewCode> [--profile name] [--json]
|
|
116
|
-
openxiangda
|
|
117
|
-
openxiangda
|
|
118
|
-
openxiangda
|
|
133
|
+
openxiangda data-view list|get|create|update|upsert|delete|status|refresh|query|stats <dataViewCode> [--profile name] [--json]
|
|
134
|
+
openxiangda route list|get|create|update|upsert|delete [--json-file file] [--dry-run] [--write-manifest]
|
|
135
|
+
openxiangda public-access list|get|create|update|upsert|delete|ticket-create|session-test|grant-check [--json-file file]
|
|
136
|
+
openxiangda auth-config list|get|create|update|upsert|delete|methods [--json-file file]
|
|
137
|
+
openxiangda function list|get|create|update|upsert|delete|invoke [--json-file file]
|
|
138
|
+
openxiangda connector list|get|create|update|upsert|delete|invoke|download-test [--json-file file]
|
|
139
|
+
openxiangda notification template-list|template-get|template-upsert|template-delete|type-list|type-get|type-upsert|type-delete|preview|send|batch-send
|
|
140
|
+
openxiangda permission role-list|role-create|role-update|role-delete|role-bind|audit
|
|
141
|
+
openxiangda permission page-group-list|page-group-create|page-group-update|page-group-delete|page-group-bind
|
|
142
|
+
openxiangda permission form-group-list|form-group-create|form-group-update|form-group-delete|form-group-bind
|
|
119
143
|
openxiangda settings get|save|indexes|indexes-save|data-management|data-management-save|public-access
|
|
120
|
-
openxiangda resource validate|plan|publish|pull [--profile name] [--json]
|
|
144
|
+
openxiangda resource validate|plan|publish|pull|typegen|explain [--profile name] [--json]
|
|
121
145
|
openxiangda runtime deploy [--profile name] [--dist dist] [--build-id id] [--upload-mode staged|legacy-json] [--upload-timeout-ms ms] [--no-build] [--no-activate] [--json]
|
|
122
146
|
openxiangda runtime releases [--profile name] [--json]
|
|
123
147
|
openxiangda runtime activate <releaseId> [--profile name] [--json]
|
|
@@ -152,6 +176,7 @@ const SUBCOMMAND_BOOLEAN_FLAGS = new Set([
|
|
|
152
176
|
'--strict',
|
|
153
177
|
'--summary',
|
|
154
178
|
'--unpublish',
|
|
179
|
+
'--write-manifest',
|
|
155
180
|
'--yes',
|
|
156
181
|
'-h',
|
|
157
182
|
]);
|
|
@@ -212,6 +237,10 @@ async function update(args) {
|
|
|
212
237
|
const requestedSubcommand = args[0] && !args[0].startsWith('--') ? args[0] : 'check';
|
|
213
238
|
const parsedArgs = requestedSubcommand === args[0] ? args.slice(1) : args;
|
|
214
239
|
const { flags } = parseArgs(parsedArgs);
|
|
240
|
+
if (wantsSubcommandHelp(requestedSubcommand, flags)) {
|
|
241
|
+
print('用法: openxiangda update check|install [--json] [--registry https://registry.npmjs.org] [--no-skills]');
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
215
244
|
const registry = normalizeNpmRegistry(flags.registry || OFFICIAL_NPM_REGISTRY);
|
|
216
245
|
|
|
217
246
|
if (requestedSubcommand === 'check') {
|
|
@@ -401,9 +430,165 @@ function parseSemver(value) {
|
|
|
401
430
|
};
|
|
402
431
|
}
|
|
403
432
|
|
|
433
|
+
async function design(args) {
|
|
434
|
+
const { subcommand, rest } = parseSubcommandArgs(args);
|
|
435
|
+
const { flags, positional } = parseArgs(rest);
|
|
436
|
+
const topic = flags.topic || positional[0];
|
|
437
|
+
if (wantsSubcommandHelp(subcommand, flags)) {
|
|
438
|
+
print('用法: openxiangda design gates|template [--topic new-app,public-access] [--json]');
|
|
439
|
+
return;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
if (subcommand === 'gates') {
|
|
443
|
+
const result = getDesignGates(topic);
|
|
444
|
+
if (flags.json) return writeJson(result);
|
|
445
|
+
print(renderDesignGatesText(topic));
|
|
446
|
+
return;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
if (subcommand === 'template') {
|
|
450
|
+
const result = {
|
|
451
|
+
topic: topic || 'all',
|
|
452
|
+
content: renderDesignTemplate(topic),
|
|
453
|
+
};
|
|
454
|
+
if (flags.json) return writeJson(result);
|
|
455
|
+
print(result.content);
|
|
456
|
+
return;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
fail('用法: openxiangda design gates|template [--topic code] [--json]');
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
async function doctor(args) {
|
|
463
|
+
const { flags } = parseArgs(args);
|
|
464
|
+
if (flags.help || flags.h) {
|
|
465
|
+
print('用法: openxiangda doctor [--profile name] [--app-type APP_XXX] [--agent codex] [--json]');
|
|
466
|
+
return;
|
|
467
|
+
}
|
|
468
|
+
const config = loadConfig();
|
|
469
|
+
const profileName = flags.profile || config.currentProfile;
|
|
470
|
+
const result = {
|
|
471
|
+
cli: {
|
|
472
|
+
version: CURRENT_VERSION,
|
|
473
|
+
commandDiscovery: 'openxiangda commands --json',
|
|
474
|
+
designGateCommand: 'openxiangda design gates --json',
|
|
475
|
+
},
|
|
476
|
+
profile: {
|
|
477
|
+
requested: profileName || null,
|
|
478
|
+
exists: false,
|
|
479
|
+
baseUrl: null,
|
|
480
|
+
loggedIn: false,
|
|
481
|
+
user: null,
|
|
482
|
+
},
|
|
483
|
+
workspace: {
|
|
484
|
+
cwd: process.cwd(),
|
|
485
|
+
stateFile: path.join(process.cwd(), PROJECT_STATE_FILE),
|
|
486
|
+
hasState: fs.existsSync(path.join(process.cwd(), PROJECT_STATE_FILE)),
|
|
487
|
+
appType: flags['app-type'] || null,
|
|
488
|
+
bound: false,
|
|
489
|
+
},
|
|
490
|
+
resources: {
|
|
491
|
+
hasDirectory: fs.existsSync(path.join(process.cwd(), 'src', 'resources')),
|
|
492
|
+
validation: null,
|
|
493
|
+
},
|
|
494
|
+
skills: null,
|
|
495
|
+
checks: [],
|
|
496
|
+
};
|
|
497
|
+
|
|
498
|
+
if (profileName && config.profiles?.[profileName]) {
|
|
499
|
+
const { profile } = getProfile(config, profileName);
|
|
500
|
+
result.profile.exists = true;
|
|
501
|
+
result.profile.baseUrl = profile.baseUrl || null;
|
|
502
|
+
result.profile.loggedIn = Boolean(profile.token?.accessToken);
|
|
503
|
+
result.profile.user = profile.user || null;
|
|
504
|
+
try {
|
|
505
|
+
const target = getWorkspaceTarget(config, profileName, flags);
|
|
506
|
+
result.workspace.appType = target.appType;
|
|
507
|
+
result.workspace.bound = true;
|
|
508
|
+
result.workspace.profile = target.profileName;
|
|
509
|
+
result.workspace.boundAt = target.bound.updatedAt || null;
|
|
510
|
+
result.checks.push({ name: 'workspace-binding', status: 'ok' });
|
|
511
|
+
} catch (error) {
|
|
512
|
+
result.checks.push({ name: 'workspace-binding', status: 'warn', message: error.message });
|
|
513
|
+
}
|
|
514
|
+
if (result.profile.loggedIn) {
|
|
515
|
+
try {
|
|
516
|
+
const authStatus = await requestWithAuth(config, profileName, '/openxiangda-api/v1/auth/whoami');
|
|
517
|
+
result.profile.authStatus = authStatus;
|
|
518
|
+
result.checks.push({ name: 'auth', status: 'ok' });
|
|
519
|
+
} catch (error) {
|
|
520
|
+
result.profile.authError = error.message;
|
|
521
|
+
result.checks.push({ name: 'auth', status: 'warn', message: error.message });
|
|
522
|
+
}
|
|
523
|
+
} else {
|
|
524
|
+
result.checks.push({ name: 'auth', status: 'warn', message: 'profile 未登录' });
|
|
525
|
+
}
|
|
526
|
+
} else {
|
|
527
|
+
result.checks.push({ name: 'profile', status: 'warn', message: '未选择或不存在 profile' });
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
if (result.resources.hasDirectory) {
|
|
531
|
+
const manifest = loadWorkspaceResources();
|
|
532
|
+
const validation = validateWorkspaceResources(manifest);
|
|
533
|
+
result.resources.validation = {
|
|
534
|
+
errors: validation.errors,
|
|
535
|
+
warnings: validation.warnings,
|
|
536
|
+
counts: Object.fromEntries(
|
|
537
|
+
RESOURCE_SPECS.map(spec => [spec.key, (manifest[spec.key] || []).length])
|
|
538
|
+
),
|
|
539
|
+
};
|
|
540
|
+
result.checks.push({
|
|
541
|
+
name: 'resource-validate',
|
|
542
|
+
status: validation.errors.length > 0 ? 'error' : 'ok',
|
|
543
|
+
errors: validation.errors.length,
|
|
544
|
+
warnings: validation.warnings.length,
|
|
545
|
+
});
|
|
546
|
+
} else {
|
|
547
|
+
result.checks.push({ name: 'resource-validate', status: 'skip', message: '未发现 src/resources' });
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
result.skills = getSkillStatusReport({ agent: flags.agent || 'codex' });
|
|
551
|
+
const outdatedSkills = result.skills.results
|
|
552
|
+
.flatMap(item => item.skills)
|
|
553
|
+
.filter(item => item.status !== 'installed');
|
|
554
|
+
result.checks.push({
|
|
555
|
+
name: 'skills',
|
|
556
|
+
status: outdatedSkills.length > 0 ? 'warn' : 'ok',
|
|
557
|
+
message: outdatedSkills.length > 0 ? `${outdatedSkills.length} 个 skill 未安装或过期` : undefined,
|
|
558
|
+
});
|
|
559
|
+
|
|
560
|
+
if (flags.json) return writeJson(result);
|
|
561
|
+
printDoctorReport(result);
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
function printDoctorReport(result) {
|
|
565
|
+
const lines = [
|
|
566
|
+
`OpenXiangda doctor v${result.cli.version}`,
|
|
567
|
+
`profile: ${result.profile.requested || '(none)'} ${result.profile.loggedIn ? '(logged in)' : '(not logged in)'}`,
|
|
568
|
+
`baseUrl: ${result.profile.baseUrl || '(none)'}`,
|
|
569
|
+
`workspace: ${result.workspace.bound ? `${result.workspace.profile}/${result.workspace.appType}` : 'not bound'}`,
|
|
570
|
+
`resources: ${result.resources.hasDirectory ? 'src/resources found' : 'missing'}`,
|
|
571
|
+
];
|
|
572
|
+
if (result.resources.validation) {
|
|
573
|
+
lines.push(
|
|
574
|
+
`resource validation: ${result.resources.validation.errors.length} errors, ${result.resources.validation.warnings.length} warnings`
|
|
575
|
+
);
|
|
576
|
+
}
|
|
577
|
+
for (const check of result.checks) {
|
|
578
|
+
const suffix = check.message ? ` - ${check.message}` : '';
|
|
579
|
+
lines.push(`- ${check.name}: ${check.status}${suffix}`);
|
|
580
|
+
}
|
|
581
|
+
lines.push(`design gate: ${result.cli.designGateCommand}`);
|
|
582
|
+
print(lines.join('\n'));
|
|
583
|
+
}
|
|
584
|
+
|
|
404
585
|
async function platform(args) {
|
|
405
586
|
const [subcommand, ...rest] = args;
|
|
406
587
|
const { flags, positional } = parseArgs(rest);
|
|
588
|
+
if (wantsSubcommandHelp(subcommand, flags)) {
|
|
589
|
+
print('用法: openxiangda platform add|list|use|remove');
|
|
590
|
+
return;
|
|
591
|
+
}
|
|
407
592
|
const config = loadConfig();
|
|
408
593
|
|
|
409
594
|
if (subcommand === 'add') {
|
|
@@ -479,6 +664,16 @@ async function platform(args) {
|
|
|
479
664
|
|
|
480
665
|
async function login(args) {
|
|
481
666
|
const { flags, positional } = parseArgs(args);
|
|
667
|
+
if (
|
|
668
|
+
flags.help ||
|
|
669
|
+
flags.h ||
|
|
670
|
+
positional[0] === 'help' ||
|
|
671
|
+
positional[0] === '--help' ||
|
|
672
|
+
positional[0] === '-h'
|
|
673
|
+
) {
|
|
674
|
+
print('用法: openxiangda login <platform-url> [--profile name] [--no-open] [--json]');
|
|
675
|
+
return;
|
|
676
|
+
}
|
|
482
677
|
const config = loadConfig();
|
|
483
678
|
const rawUrl = positional[0];
|
|
484
679
|
const profileName = flags.profile || config.currentProfile || 'default';
|
|
@@ -592,6 +787,10 @@ function normalizePlatformSessionUrl(value, baseUrl) {
|
|
|
592
787
|
async function auth(args) {
|
|
593
788
|
const [subcommand, ...rest] = args;
|
|
594
789
|
const { flags } = parseArgs(rest);
|
|
790
|
+
if (wantsSubcommandHelp(subcommand, flags)) {
|
|
791
|
+
print('用法: openxiangda auth status|refresh|logout [--profile name] [--json]');
|
|
792
|
+
return;
|
|
793
|
+
}
|
|
595
794
|
|
|
596
795
|
if (subcommand === 'status') {
|
|
597
796
|
const config = loadConfig();
|
|
@@ -646,6 +845,10 @@ async function auth(args) {
|
|
|
646
845
|
|
|
647
846
|
async function env(args) {
|
|
648
847
|
const { flags } = parseArgs(args);
|
|
848
|
+
if (flags.help || flags.h) {
|
|
849
|
+
print('用法: openxiangda env [--profile name] [--json]');
|
|
850
|
+
return;
|
|
851
|
+
}
|
|
649
852
|
const config = loadConfig();
|
|
650
853
|
const globalEnv = loadGlobalEnv();
|
|
651
854
|
const profileName = flags.profile || config.currentProfile;
|
|
@@ -683,6 +886,12 @@ async function env(args) {
|
|
|
683
886
|
async function feedback(args) {
|
|
684
887
|
const [subcommand, ...rest] = args;
|
|
685
888
|
const { flags, positional } = parseArgs(rest);
|
|
889
|
+
if (wantsSubcommandHelp(subcommand, flags)) {
|
|
890
|
+
print(
|
|
891
|
+
'用法: openxiangda feedback preview|submit --summary <text> [--type bug] [--severity medium] [--profile name] [--yes] [--json]'
|
|
892
|
+
);
|
|
893
|
+
return;
|
|
894
|
+
}
|
|
686
895
|
|
|
687
896
|
if (subcommand !== 'preview' && subcommand !== 'submit') {
|
|
688
897
|
fail(
|
|
@@ -1106,6 +1315,10 @@ function summarizeOssEnv(globalEnv) {
|
|
|
1106
1315
|
async function workspace(args) {
|
|
1107
1316
|
const [subcommand, ...rest] = args;
|
|
1108
1317
|
const { flags, positional } = parseArgs(rest);
|
|
1318
|
+
if (wantsSubcommandHelp(subcommand, flags)) {
|
|
1319
|
+
print('用法: openxiangda workspace init|bind|publish [--changed [--since ref]|--form code|--page code|--only list] [--dry-run] [--force] [--resources|--skip-resources]');
|
|
1320
|
+
return;
|
|
1321
|
+
}
|
|
1109
1322
|
const config = loadConfig();
|
|
1110
1323
|
|
|
1111
1324
|
if (subcommand === 'init') {
|
|
@@ -1238,6 +1451,10 @@ async function workspace(args) {
|
|
|
1238
1451
|
async function app(args) {
|
|
1239
1452
|
const [subcommand, ...rest] = args;
|
|
1240
1453
|
const { flags, positional } = parseArgs(rest);
|
|
1454
|
+
if (wantsSubcommandHelp(subcommand, flags)) {
|
|
1455
|
+
print('用法: openxiangda app list|create|snapshot [--profile name] [--json]');
|
|
1456
|
+
return;
|
|
1457
|
+
}
|
|
1241
1458
|
const config = loadConfig();
|
|
1242
1459
|
const profileName = flags.profile || config.currentProfile;
|
|
1243
1460
|
|
|
@@ -1308,6 +1525,10 @@ function extractCreatedAppType(data) {
|
|
|
1308
1525
|
async function form(args) {
|
|
1309
1526
|
const [subcommand, ...rest] = args;
|
|
1310
1527
|
const { flags, positional } = parseArgs(rest);
|
|
1528
|
+
if (wantsSubcommandHelp(subcommand, flags)) {
|
|
1529
|
+
print('用法: openxiangda form list|create|bind|pull|publish [--profile name] [--json]');
|
|
1530
|
+
return;
|
|
1531
|
+
}
|
|
1311
1532
|
const config = loadConfig();
|
|
1312
1533
|
const profileName = flags.profile || config.currentProfile;
|
|
1313
1534
|
|
|
@@ -1416,6 +1637,10 @@ async function form(args) {
|
|
|
1416
1637
|
async function page(args) {
|
|
1417
1638
|
const [subcommand, ...rest] = args;
|
|
1418
1639
|
const { flags, positional } = parseArgs(rest);
|
|
1640
|
+
if (wantsSubcommandHelp(subcommand, flags)) {
|
|
1641
|
+
print('用法: openxiangda page list|publish|bind|releases|activate [--profile name] [--json]');
|
|
1642
|
+
return;
|
|
1643
|
+
}
|
|
1419
1644
|
const config = loadConfig();
|
|
1420
1645
|
const profileName = flags.profile || config.currentProfile;
|
|
1421
1646
|
|
|
@@ -1536,6 +1761,10 @@ async function page(args) {
|
|
|
1536
1761
|
async function menu(args) {
|
|
1537
1762
|
const [subcommand, ...rest] = args;
|
|
1538
1763
|
const { flags, positional } = parseArgs(rest);
|
|
1764
|
+
if (wantsSubcommandHelp(subcommand, flags)) {
|
|
1765
|
+
print('用法: openxiangda menu list|create|update|sort|bind|delete [--profile name] [--json]');
|
|
1766
|
+
return;
|
|
1767
|
+
}
|
|
1539
1768
|
const config = loadConfig();
|
|
1540
1769
|
const profileName = flags.profile || config.currentProfile;
|
|
1541
1770
|
|
|
@@ -1626,7 +1855,46 @@ async function menu(args) {
|
|
|
1626
1855
|
return;
|
|
1627
1856
|
}
|
|
1628
1857
|
|
|
1629
|
-
|
|
1858
|
+
if (subcommand === 'update') {
|
|
1859
|
+
const [menuKey] = positional;
|
|
1860
|
+
const body = readDirectJsonBody(flags, 'menu update');
|
|
1861
|
+
const menuCode = menuKey || body.code || body.resourceCode;
|
|
1862
|
+
if (!menuCode) fail('用法: openxiangda menu update <menuCode|menuId> --json-file file');
|
|
1863
|
+
const target = getWorkspaceTarget(config, profileName, flags);
|
|
1864
|
+
const menuId = resolveMenuId(target.bound, menuCode, flags);
|
|
1865
|
+
const desired = normalizeMenuDirectBody(target.bound, { ...body, code: body.code || menuCode });
|
|
1866
|
+
const data = await runDirectRequest(config, target, flags, {
|
|
1867
|
+
method: 'PUT',
|
|
1868
|
+
path: `/openxiangda-api/v1/apps/${encodeURIComponent(target.appType)}/menus/${encodeURIComponent(menuId)}`,
|
|
1869
|
+
body: desired,
|
|
1870
|
+
}, { returnData: true });
|
|
1871
|
+
if (!flags['dry-run']) {
|
|
1872
|
+
const resultCode = body.code || body.resourceCode || menuCode;
|
|
1873
|
+
if (data?.id) {
|
|
1874
|
+
saveMenuResource(target, resultCode, data.id, {
|
|
1875
|
+
formUuid: data.formUuid || desired.formUuid,
|
|
1876
|
+
pageId: data.pageId || desired.pageId,
|
|
1877
|
+
parentId: data.parentId || desired.parentId,
|
|
1878
|
+
routeCode: data.routeCode || desired.routeCode,
|
|
1879
|
+
path: data.path || desired.path,
|
|
1880
|
+
});
|
|
1881
|
+
}
|
|
1882
|
+
if (flags['write-manifest']) writeDirectManifest('menu', resultCode, { ...body, code: resultCode });
|
|
1883
|
+
}
|
|
1884
|
+
return outputDirectResult(data, flags);
|
|
1885
|
+
}
|
|
1886
|
+
|
|
1887
|
+
if (subcommand === 'sort') {
|
|
1888
|
+
const target = getWorkspaceTarget(config, profileName, flags);
|
|
1889
|
+
const body = readDirectJsonBody(flags, 'menu sort');
|
|
1890
|
+
return runDirectRequest(config, target, flags, {
|
|
1891
|
+
method: 'POST',
|
|
1892
|
+
path: `/openxiangda-api/v1/apps/${encodeURIComponent(target.appType)}/menus/sort`,
|
|
1893
|
+
body,
|
|
1894
|
+
});
|
|
1895
|
+
}
|
|
1896
|
+
|
|
1897
|
+
fail('用法: openxiangda menu list|create|update|sort|bind|delete');
|
|
1630
1898
|
}
|
|
1631
1899
|
|
|
1632
1900
|
async function workflow(args) {
|
|
@@ -2093,7 +2361,7 @@ async function dataView(args) {
|
|
|
2093
2361
|
const { subcommand, rest } = parseSubcommandArgs(args);
|
|
2094
2362
|
const { flags, positional } = parseArgs(rest);
|
|
2095
2363
|
if (wantsSubcommandHelp(subcommand, flags)) {
|
|
2096
|
-
print('用法: openxiangda data-view list|status|refresh|query|stats <dataViewCode> [--profile name] [--json]');
|
|
2364
|
+
print('用法: openxiangda data-view list|get|create|update|upsert|delete|status|refresh|query|stats <dataViewCode> [--profile name] [--json]');
|
|
2097
2365
|
return;
|
|
2098
2366
|
}
|
|
2099
2367
|
const config = loadConfig();
|
|
@@ -2176,12 +2444,418 @@ async function dataView(args) {
|
|
|
2176
2444
|
return;
|
|
2177
2445
|
}
|
|
2178
2446
|
|
|
2179
|
-
|
|
2447
|
+
if (['get', 'create', 'update', 'upsert', 'delete'].includes(subcommand)) {
|
|
2448
|
+
return directResourceCrud(config, target, {
|
|
2449
|
+
commandName: 'data-view',
|
|
2450
|
+
subcommand,
|
|
2451
|
+
flags,
|
|
2452
|
+
positional,
|
|
2453
|
+
resourceType: 'data-view',
|
|
2454
|
+
basePath: `/openxiangda-api/v1/apps/${encodeURIComponent(target.appType)}/data-views`,
|
|
2455
|
+
manifestDir: path.join('src', 'resources', 'data-views'),
|
|
2456
|
+
normalizeBody: body => normalizeDataViewManifest(target.bound, body),
|
|
2457
|
+
codeOf: body => body.code || body.resourceCode || body.definition?.code,
|
|
2458
|
+
saveState: (code, data) => saveDataViewResource(target, code, data?.id, {
|
|
2459
|
+
materializedViewName: data?.materializedViewName,
|
|
2460
|
+
status: data?.status,
|
|
2461
|
+
storageMode: data?.storageMode,
|
|
2462
|
+
}),
|
|
2463
|
+
});
|
|
2464
|
+
}
|
|
2465
|
+
|
|
2466
|
+
fail('用法: openxiangda data-view list|get|create|update|upsert|delete|status|refresh|query|stats');
|
|
2467
|
+
}
|
|
2468
|
+
|
|
2469
|
+
async function route(args) {
|
|
2470
|
+
const { subcommand, rest } = parseSubcommandArgs(args);
|
|
2471
|
+
const { flags, positional } = parseArgs(rest);
|
|
2472
|
+
if (wantsSubcommandHelp(subcommand, flags)) {
|
|
2473
|
+
print('用法: openxiangda route list|get|create|update|upsert|delete [routeCode] [--json-file file] [--dry-run] [--write-manifest]');
|
|
2474
|
+
return;
|
|
2475
|
+
}
|
|
2476
|
+
const config = loadConfig();
|
|
2477
|
+
const target = getWorkspaceTarget(config, flags.profile || config.currentProfile, flags);
|
|
2478
|
+
return directResourceCrud(config, target, {
|
|
2479
|
+
commandName: 'route',
|
|
2480
|
+
subcommand,
|
|
2481
|
+
flags,
|
|
2482
|
+
positional,
|
|
2483
|
+
resourceType: 'route',
|
|
2484
|
+
basePath: `/openxiangda-api/v1/apps/${encodeURIComponent(target.appType)}/routes`,
|
|
2485
|
+
manifestDir: path.join('src', 'resources', 'routes'),
|
|
2486
|
+
normalizeBody: body => normalizeRouteManifest(body),
|
|
2487
|
+
codeOf: body => body.code || body.resourceCode,
|
|
2488
|
+
saveState: (code, data) => saveRouteResource(target, code, data?.id, {
|
|
2489
|
+
pathPattern: data?.pathPattern,
|
|
2490
|
+
publicAccess: data?.publicAccess,
|
|
2491
|
+
publicPolicyCode: data?.publicPolicyCode,
|
|
2492
|
+
}),
|
|
2493
|
+
});
|
|
2494
|
+
}
|
|
2495
|
+
|
|
2496
|
+
async function publicAccess(args) {
|
|
2497
|
+
const { subcommand, rest } = parseSubcommandArgs(args);
|
|
2498
|
+
const { flags, positional } = parseArgs(rest);
|
|
2499
|
+
if (wantsSubcommandHelp(subcommand, flags)) {
|
|
2500
|
+
print('用法: openxiangda public-access list|get|create|update|upsert|delete|ticket-create|session-test|grant-check [policyCode] [--json-file file]');
|
|
2501
|
+
return;
|
|
2502
|
+
}
|
|
2503
|
+
const config = loadConfig();
|
|
2504
|
+
const target = getWorkspaceTarget(config, flags.profile || config.currentProfile, flags);
|
|
2505
|
+
|
|
2506
|
+
if (['list', 'get', 'create', 'update', 'upsert', 'delete'].includes(subcommand)) {
|
|
2507
|
+
return directResourceCrud(config, target, {
|
|
2508
|
+
commandName: 'public-access',
|
|
2509
|
+
subcommand,
|
|
2510
|
+
flags,
|
|
2511
|
+
positional,
|
|
2512
|
+
resourceType: 'public-access',
|
|
2513
|
+
basePath: `/openxiangda-api/v1/apps/${encodeURIComponent(target.appType)}/public-access/policies`,
|
|
2514
|
+
manifestDir: path.join('src', 'resources', 'public-access'),
|
|
2515
|
+
normalizeBody: body => normalizePublicAccessPolicyManifest(target.bound, body),
|
|
2516
|
+
codeOf: body => body.code || body.resourceCode,
|
|
2517
|
+
saveState: (code, data) => savePublicAccessPolicyResource(target, code, data?.id, {
|
|
2518
|
+
mode: data?.mode,
|
|
2519
|
+
routeCode: data?.routeCode,
|
|
2520
|
+
pathPattern: data?.pathPattern,
|
|
2521
|
+
}),
|
|
2522
|
+
});
|
|
2523
|
+
}
|
|
2524
|
+
|
|
2525
|
+
if (subcommand === 'ticket-create') {
|
|
2526
|
+
const [policyCode] = positional;
|
|
2527
|
+
if (!policyCode) fail('用法: openxiangda public-access ticket-create <policyCode> [--json-file file]');
|
|
2528
|
+
const body = readDirectJsonBody(flags, 'ticket');
|
|
2529
|
+
return runDirectRequest(config, target, flags, {
|
|
2530
|
+
method: 'POST',
|
|
2531
|
+
path: `/openxiangda-api/v1/apps/${encodeURIComponent(target.appType)}/public-access/policies/${encodeURIComponent(policyCode)}/tickets`,
|
|
2532
|
+
body,
|
|
2533
|
+
strictEnvelope: true,
|
|
2534
|
+
});
|
|
2535
|
+
}
|
|
2536
|
+
|
|
2537
|
+
if (subcommand === 'session-test') {
|
|
2538
|
+
const [policyCode] = positional;
|
|
2539
|
+
const body = {
|
|
2540
|
+
...readDirectJsonBody(flags, 'public-session', { optional: true }),
|
|
2541
|
+
...(policyCode ? { policyCode } : {}),
|
|
2542
|
+
...(flags.path ? { path: flags.path } : {}),
|
|
2543
|
+
...(flags.ticket ? { ticket: flags.ticket } : {}),
|
|
2544
|
+
};
|
|
2545
|
+
return runDirectRequest(config, target, flags, {
|
|
2546
|
+
method: 'POST',
|
|
2547
|
+
path: `/openxiangda-api/v1/apps/${encodeURIComponent(target.appType)}/public/session`,
|
|
2548
|
+
body,
|
|
2549
|
+
auth: false,
|
|
2550
|
+
strictEnvelope: true,
|
|
2551
|
+
});
|
|
2552
|
+
}
|
|
2553
|
+
|
|
2554
|
+
if (subcommand === 'grant-check') {
|
|
2555
|
+
const [policyCode] = positional;
|
|
2556
|
+
if (!policyCode) fail('用法: openxiangda public-access grant-check <policyCode> [--form-code code] [--data-view code] [--function code] [--connector code]');
|
|
2557
|
+
const policy = flags['json-file']
|
|
2558
|
+
? readDirectJsonBody(flags, 'policy')
|
|
2559
|
+
: await requestWithAuth(
|
|
2560
|
+
config,
|
|
2561
|
+
target.profileName,
|
|
2562
|
+
`/openxiangda-api/v1/apps/${encodeURIComponent(target.appType)}/public-access/policies/${encodeURIComponent(policyCode)}`
|
|
2563
|
+
);
|
|
2564
|
+
const normalized = normalizePublicAccessPolicyManifest(target.bound, policy);
|
|
2565
|
+
const grants = normalized.grants || {};
|
|
2566
|
+
const checks = buildPublicGrantChecks(target.bound, grants, flags);
|
|
2567
|
+
const result = { policyCode, grants, checks, allowed: checks.every(item => item.allowed) };
|
|
2568
|
+
if (flags.json) return writeJson(result);
|
|
2569
|
+
print(JSON.stringify(result, null, 2));
|
|
2570
|
+
if (!result.allowed) fail('公开访问 grant-check 失败');
|
|
2571
|
+
return;
|
|
2572
|
+
}
|
|
2573
|
+
|
|
2574
|
+
fail('用法: openxiangda public-access list|get|create|update|upsert|delete|ticket-create|session-test|grant-check');
|
|
2575
|
+
}
|
|
2576
|
+
|
|
2577
|
+
async function authConfig(args) {
|
|
2578
|
+
const { subcommand, rest } = parseSubcommandArgs(args);
|
|
2579
|
+
const { flags, positional } = parseArgs(rest);
|
|
2580
|
+
if (wantsSubcommandHelp(subcommand, flags)) {
|
|
2581
|
+
print('用法: openxiangda auth-config list|get|create|update|upsert|delete|methods [configCode] [--json-file file]');
|
|
2582
|
+
return;
|
|
2583
|
+
}
|
|
2584
|
+
if (subcommand === 'methods') {
|
|
2585
|
+
const result = {
|
|
2586
|
+
methods: [
|
|
2587
|
+
{ type: 'password', requiredFields: ['username', 'password'] },
|
|
2588
|
+
{ type: 'phone_code', requiredFields: ['phone', 'code'], provider: 'functionCode 或 connector' },
|
|
2589
|
+
{ type: 'sso', requiredFields: ['provider', 'callbackUrl'] },
|
|
2590
|
+
],
|
|
2591
|
+
defaultRegistration: { mode: 'reject' },
|
|
2592
|
+
defaultBinding: { mode: 'auto' },
|
|
2593
|
+
};
|
|
2594
|
+
if (flags.json) return writeJson(result);
|
|
2595
|
+
print(JSON.stringify(result, null, 2));
|
|
2596
|
+
return;
|
|
2597
|
+
}
|
|
2598
|
+
const config = loadConfig();
|
|
2599
|
+
const target = getWorkspaceTarget(config, flags.profile || config.currentProfile, flags);
|
|
2600
|
+
return directResourceCrud(config, target, {
|
|
2601
|
+
commandName: 'auth-config',
|
|
2602
|
+
subcommand,
|
|
2603
|
+
flags,
|
|
2604
|
+
positional,
|
|
2605
|
+
resourceType: 'auth-config',
|
|
2606
|
+
basePath: `/openxiangda-api/v1/apps/${encodeURIComponent(target.appType)}/auth/configs`,
|
|
2607
|
+
manifestDir: path.join('src', 'resources', 'auth'),
|
|
2608
|
+
normalizeBody: body => normalizeAuthConfigManifest(body),
|
|
2609
|
+
codeOf: body => body.code || body.resourceCode || 'default',
|
|
2610
|
+
saveState: (code, data) => saveAuthConfigResource(target, code, data?.id, {
|
|
2611
|
+
status: data?.status,
|
|
2612
|
+
}),
|
|
2613
|
+
});
|
|
2614
|
+
}
|
|
2615
|
+
|
|
2616
|
+
async function appFunction(args) {
|
|
2617
|
+
const { subcommand, rest } = parseSubcommandArgs(args);
|
|
2618
|
+
const { flags, positional } = parseArgs(rest);
|
|
2619
|
+
if (wantsSubcommandHelp(subcommand, flags)) {
|
|
2620
|
+
print('用法: openxiangda function list|get|create|update|upsert|delete|invoke [functionCode] [--json-file file]');
|
|
2621
|
+
return;
|
|
2622
|
+
}
|
|
2623
|
+
const config = loadConfig();
|
|
2624
|
+
const target = getWorkspaceTarget(config, flags.profile || config.currentProfile, flags);
|
|
2625
|
+
|
|
2626
|
+
if (subcommand === 'invoke') {
|
|
2627
|
+
const [functionCode] = positional;
|
|
2628
|
+
if (!functionCode) fail('用法: openxiangda function invoke <functionCode> [--body-json file|json]');
|
|
2629
|
+
const body = readDirectJsonBody(flags, 'function invoke', { optional: true });
|
|
2630
|
+
return runDirectRequest(config, target, flags, {
|
|
2631
|
+
method: 'POST',
|
|
2632
|
+
path: `/${encodeURIComponent(target.appType)}/v1/functions/${encodeURIComponent(functionCode)}/invoke.json`,
|
|
2633
|
+
body,
|
|
2634
|
+
strictEnvelope: true,
|
|
2635
|
+
});
|
|
2636
|
+
}
|
|
2637
|
+
|
|
2638
|
+
return directResourceCrud(config, target, {
|
|
2639
|
+
commandName: 'function',
|
|
2640
|
+
subcommand,
|
|
2641
|
+
flags,
|
|
2642
|
+
positional,
|
|
2643
|
+
resourceType: 'function',
|
|
2644
|
+
basePath: `/openxiangda-api/v1/apps/${encodeURIComponent(target.appType)}/functions`,
|
|
2645
|
+
manifestDir: path.join('src', 'resources', 'functions'),
|
|
2646
|
+
prepareBody: async body => {
|
|
2647
|
+
const definitionJson = await resolveManifestJson(
|
|
2648
|
+
config,
|
|
2649
|
+
target.profileName,
|
|
2650
|
+
body,
|
|
2651
|
+
'definitionJson',
|
|
2652
|
+
'definitionFile'
|
|
2653
|
+
);
|
|
2654
|
+
applyResourceBindingsToRuntimeDefinition(target.bound, body, definitionJson);
|
|
2655
|
+
const code = body.code || body.functionCode || body.resourceCode;
|
|
2656
|
+
return stripUndefinedValues({
|
|
2657
|
+
code,
|
|
2658
|
+
name: body.name || definitionJson.name || code,
|
|
2659
|
+
description: body.description !== undefined ? body.description : definitionJson.description || '',
|
|
2660
|
+
definitionJson,
|
|
2661
|
+
resourceBindings: definitionJson.resourceBindings,
|
|
2662
|
+
inputSchema: body.inputSchema || definitionJson.inputSchema,
|
|
2663
|
+
outputSchema: body.outputSchema || definitionJson.outputSchema,
|
|
2664
|
+
status: body.status,
|
|
2665
|
+
});
|
|
2666
|
+
},
|
|
2667
|
+
codeOf: body => body.code || body.functionCode || body.resourceCode,
|
|
2668
|
+
saveState: (code, data) => saveFunctionResource(target, code, data?.id, {
|
|
2669
|
+
status: data?.status,
|
|
2670
|
+
}),
|
|
2671
|
+
});
|
|
2672
|
+
}
|
|
2673
|
+
|
|
2674
|
+
async function connector(args) {
|
|
2675
|
+
const { subcommand, rest } = parseSubcommandArgs(args);
|
|
2676
|
+
const { flags, positional } = parseArgs(rest);
|
|
2677
|
+
if (wantsSubcommandHelp(subcommand, flags)) {
|
|
2678
|
+
print('用法: openxiangda connector list|get|create|update|upsert|delete|invoke|download-test [connectorCode[.apiCode]] [--json-file file]');
|
|
2679
|
+
return;
|
|
2680
|
+
}
|
|
2681
|
+
const config = loadConfig();
|
|
2682
|
+
const target = getWorkspaceTarget(config, flags.profile || config.currentProfile, flags);
|
|
2683
|
+
|
|
2684
|
+
if (subcommand === 'invoke' || subcommand === 'download-test') {
|
|
2685
|
+
const targetName = positional[0] || flags.connector;
|
|
2686
|
+
if (!targetName) fail(`用法: openxiangda connector ${subcommand} <connectorCode.apiCode> [--body-json file|json]`);
|
|
2687
|
+
const parsed = parseConnectorApiName(targetName, flags);
|
|
2688
|
+
const body = {
|
|
2689
|
+
connector: parsed.connector,
|
|
2690
|
+
api: parsed.api,
|
|
2691
|
+
...readDirectJsonBody(flags, 'connector invoke', { optional: true }),
|
|
2692
|
+
...(flags['path-params-json'] ? { pathParams: readJsonArg(flags['path-params-json'], 'path-params-json') } : {}),
|
|
2693
|
+
...(flags['query-json'] ? { query: readJsonArg(flags['query-json'], 'query-json') } : {}),
|
|
2694
|
+
...(flags['headers-json'] ? { headers: readJsonArg(flags['headers-json'], 'headers-json') } : {}),
|
|
2695
|
+
...(flags['request-body-type'] ? { requestBodyType: flags['request-body-type'] } : {}),
|
|
2696
|
+
...(flags['response-type'] ? { responseType: flags['response-type'] } : {}),
|
|
2697
|
+
};
|
|
2698
|
+
if (subcommand === 'download-test') body.responseType = 'binary';
|
|
2699
|
+
return runDirectRequest(config, target, flags, {
|
|
2700
|
+
method: 'POST',
|
|
2701
|
+
path: `/${encodeURIComponent(target.appType)}/v1/connectors/actions/${subcommand === 'download-test' ? 'download' : 'invoke'}`,
|
|
2702
|
+
body,
|
|
2703
|
+
strictEnvelope: subcommand !== 'download-test',
|
|
2704
|
+
});
|
|
2705
|
+
}
|
|
2706
|
+
|
|
2707
|
+
return directResourceCrud(config, target, {
|
|
2708
|
+
commandName: 'connector',
|
|
2709
|
+
subcommand,
|
|
2710
|
+
flags,
|
|
2711
|
+
positional,
|
|
2712
|
+
resourceType: 'connector',
|
|
2713
|
+
basePath: `/openxiangda-api/v1/apps/${encodeURIComponent(target.appType)}/connectors`,
|
|
2714
|
+
manifestDir: path.join('src', 'resources', 'connectors'),
|
|
2715
|
+
createMethod: 'POST',
|
|
2716
|
+
createPath: `/openxiangda-api/v1/apps/${encodeURIComponent(target.appType)}/connectors/actions/sync`,
|
|
2717
|
+
createBody: body => ({ connectors: [normalizeConnectorManifest(body)] }),
|
|
2718
|
+
updateMethod: 'POST',
|
|
2719
|
+
updatePath: `/openxiangda-api/v1/apps/${encodeURIComponent(target.appType)}/connectors/actions/sync`,
|
|
2720
|
+
updateBody: body => ({ connectors: [normalizeConnectorManifest(body)] }),
|
|
2721
|
+
normalizeBody: body => normalizeConnectorManifest(body),
|
|
2722
|
+
codeOf: body => body.code || body.methodName,
|
|
2723
|
+
responseData: data => Array.isArray(data?.data) ? data.data[0] : data,
|
|
2724
|
+
saveState: (code, data) => saveConnectorResource(target, code, data?.connector?.id || data?.id, {
|
|
2725
|
+
apis: Object.fromEntries(
|
|
2726
|
+
(data?.apis || []).map(api => [api.code || api.methodName, { apiId: api.id, name: api.name }])
|
|
2727
|
+
),
|
|
2728
|
+
}),
|
|
2729
|
+
});
|
|
2730
|
+
}
|
|
2731
|
+
|
|
2732
|
+
async function notification(args) {
|
|
2733
|
+
const { subcommand, rest } = parseSubcommandArgs(args);
|
|
2734
|
+
const { flags, positional } = parseArgs(rest);
|
|
2735
|
+
if (wantsSubcommandHelp(subcommand, flags)) {
|
|
2736
|
+
print('用法: openxiangda notification template-list|template-get|template-upsert|template-delete|type-list|type-get|type-upsert|type-delete|preview|send|batch-send');
|
|
2737
|
+
return;
|
|
2738
|
+
}
|
|
2739
|
+
const config = loadConfig();
|
|
2740
|
+
const target = getWorkspaceTarget(config, flags.profile || config.currentProfile, flags);
|
|
2741
|
+
const appPrefix = `/openxiangda-api/v1/apps/${encodeURIComponent(target.appType)}/notifications`;
|
|
2742
|
+
|
|
2743
|
+
if (subcommand === 'template-list') {
|
|
2744
|
+
return runDirectRequest(config, target, flags, {
|
|
2745
|
+
method: 'GET',
|
|
2746
|
+
path: apiPathWithQuery(`${appPrefix}/templates`, { page: flags.page, pageSize: flags['page-size'] || flags.limit }),
|
|
2747
|
+
});
|
|
2748
|
+
}
|
|
2749
|
+
if (subcommand === 'template-get') {
|
|
2750
|
+
const code = positional[0] || flags.code;
|
|
2751
|
+
if (!code) fail('用法: openxiangda notification template-get <templateCode>');
|
|
2752
|
+
return runDirectRequest(config, target, flags, {
|
|
2753
|
+
method: 'GET',
|
|
2754
|
+
path: `${appPrefix}/templates/${encodeURIComponent(code)}`,
|
|
2755
|
+
});
|
|
2756
|
+
}
|
|
2757
|
+
if (subcommand === 'template-upsert') {
|
|
2758
|
+
const body = readDirectJsonBody(flags, 'notification template');
|
|
2759
|
+
const template = extractNotificationTemplateBody(target.bound, body);
|
|
2760
|
+
const code = positional[0] || body.code || body.templateCode || template.code;
|
|
2761
|
+
if (!code) fail('notification template-upsert 缺少 template code');
|
|
2762
|
+
const normalized = normalizeNotificationTemplateManifest(target.bound, { ...body, ...template, code });
|
|
2763
|
+
const data = await runDirectRequest(config, target, flags, {
|
|
2764
|
+
method: 'PUT',
|
|
2765
|
+
path: `${appPrefix}/templates/${encodeURIComponent(code)}`,
|
|
2766
|
+
body: normalized,
|
|
2767
|
+
}, { returnData: true });
|
|
2768
|
+
if (!flags['dry-run'] && data?.id) {
|
|
2769
|
+
saveNotificationTemplateResource(target, code, data.id, {
|
|
2770
|
+
level: data.level || normalized.level,
|
|
2771
|
+
formUuid: data.formUuid || normalized.formUuid,
|
|
2772
|
+
});
|
|
2773
|
+
if (flags['write-manifest']) writeDirectManifest('notification', code, { ...body, code, resourceType: 'template' });
|
|
2774
|
+
}
|
|
2775
|
+
return outputDirectResult(data, flags);
|
|
2776
|
+
}
|
|
2777
|
+
if (subcommand === 'template-delete') {
|
|
2778
|
+
const code = positional[0] || flags.code;
|
|
2779
|
+
if (!code || !flags.force) fail('用法: openxiangda notification template-delete <templateCode> --force');
|
|
2780
|
+
return runDirectRequest(config, target, flags, {
|
|
2781
|
+
method: 'DELETE',
|
|
2782
|
+
path: `${appPrefix}/templates/${encodeURIComponent(code)}`,
|
|
2783
|
+
});
|
|
2784
|
+
}
|
|
2785
|
+
if (subcommand === 'type-list') {
|
|
2786
|
+
return runDirectRequest(config, target, flags, {
|
|
2787
|
+
method: 'GET',
|
|
2788
|
+
path: apiPathWithQuery(`${appPrefix}/type-configs`, { page: flags.page, pageSize: flags['page-size'] || flags.limit }),
|
|
2789
|
+
});
|
|
2790
|
+
}
|
|
2791
|
+
if (subcommand === 'type-get') {
|
|
2792
|
+
const code = positional[0] || flags.code;
|
|
2793
|
+
if (!code) fail('用法: openxiangda notification type-get <notificationType>');
|
|
2794
|
+
return runDirectRequest(config, target, flags, {
|
|
2795
|
+
method: 'GET',
|
|
2796
|
+
path: `${appPrefix}/type-configs/${encodeURIComponent(code)}`,
|
|
2797
|
+
});
|
|
2798
|
+
}
|
|
2799
|
+
if (subcommand === 'type-upsert') {
|
|
2800
|
+
const body = readDirectJsonBody(flags, 'notification type-config');
|
|
2801
|
+
const configBody = extractNotificationTypeConfigBody(target.bound, body);
|
|
2802
|
+
const code = positional[0] || body.notificationType || configBody.notificationType;
|
|
2803
|
+
if (!code) fail('notification type-upsert 缺少 notificationType');
|
|
2804
|
+
const normalized = normalizeNotificationTypeConfigManifest(target.bound, { ...body, ...configBody, notificationType: code });
|
|
2805
|
+
const data = await runDirectRequest(config, target, flags, {
|
|
2806
|
+
method: 'PUT',
|
|
2807
|
+
path: `${appPrefix}/type-configs/${encodeURIComponent(code)}`,
|
|
2808
|
+
body: normalized,
|
|
2809
|
+
}, { returnData: true });
|
|
2810
|
+
if (!flags['dry-run'] && data?.id) {
|
|
2811
|
+
saveNotificationTypeConfigResource(target, body.code || code, data.id, {
|
|
2812
|
+
notificationType: data.notificationType || code,
|
|
2813
|
+
level: data.level || normalized.level,
|
|
2814
|
+
formUuid: data.formUuid || normalized.formUuid,
|
|
2815
|
+
templateId: data.templateId,
|
|
2816
|
+
templateCode: data.template?.code || body.templateCode,
|
|
2817
|
+
});
|
|
2818
|
+
if (flags['write-manifest']) writeDirectManifest('notification', body.code || code, { ...body, notificationType: code, resourceType: 'typeConfig' });
|
|
2819
|
+
}
|
|
2820
|
+
return outputDirectResult(data, flags);
|
|
2821
|
+
}
|
|
2822
|
+
if (subcommand === 'type-delete') {
|
|
2823
|
+
const code = positional[0] || flags.code;
|
|
2824
|
+
if (!code || !flags.force) fail('用法: openxiangda notification type-delete <notificationType> --force');
|
|
2825
|
+
return runDirectRequest(config, target, flags, {
|
|
2826
|
+
method: 'DELETE',
|
|
2827
|
+
path: `${appPrefix}/type-configs/${encodeURIComponent(code)}`,
|
|
2828
|
+
});
|
|
2829
|
+
}
|
|
2830
|
+
if (['preview', 'send', 'batch-send'].includes(subcommand)) {
|
|
2831
|
+
if ((subcommand === 'send' || subcommand === 'batch-send') && !flags.force) {
|
|
2832
|
+
fail(`openxiangda notification ${subcommand} 是发送动作,必须加 --force`);
|
|
2833
|
+
}
|
|
2834
|
+
const code = positional[0] || flags.code;
|
|
2835
|
+
const body = {
|
|
2836
|
+
...readDirectJsonBody(flags, `notification ${subcommand}`, { optional: true }),
|
|
2837
|
+
...(code ? { templateCode: code, notificationType: code } : {}),
|
|
2838
|
+
};
|
|
2839
|
+
return runDirectRequest(config, target, flags, {
|
|
2840
|
+
method: 'POST',
|
|
2841
|
+
path: `${appPrefix}/${subcommand === 'batch-send' ? 'batch-send' : subcommand}`,
|
|
2842
|
+
body,
|
|
2843
|
+
strictEnvelope: true,
|
|
2844
|
+
});
|
|
2845
|
+
}
|
|
2846
|
+
|
|
2847
|
+
fail('用法: openxiangda notification template-list|template-get|template-upsert|template-delete|type-list|type-get|type-upsert|type-delete|preview|send|batch-send');
|
|
2180
2848
|
}
|
|
2181
2849
|
|
|
2182
2850
|
async function permission(args) {
|
|
2183
2851
|
const [subcommand, ...rest] = args;
|
|
2184
2852
|
const { flags, positional } = parseArgs(rest);
|
|
2853
|
+
if (wantsSubcommandHelp(subcommand, flags)) {
|
|
2854
|
+
print(
|
|
2855
|
+
'用法: 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'
|
|
2856
|
+
);
|
|
2857
|
+
return;
|
|
2858
|
+
}
|
|
2185
2859
|
const config = loadConfig();
|
|
2186
2860
|
const profileName = flags.profile || config.currentProfile;
|
|
2187
2861
|
|
|
@@ -2243,6 +2917,46 @@ async function permission(args) {
|
|
|
2243
2917
|
return;
|
|
2244
2918
|
}
|
|
2245
2919
|
|
|
2920
|
+
if (subcommand === 'role-update') {
|
|
2921
|
+
const [roleKey] = positional;
|
|
2922
|
+
const body = readDirectJsonBody(flags, 'role update');
|
|
2923
|
+
const roleCode = roleKey || body.code || body.resourceCode;
|
|
2924
|
+
if (!roleCode) fail('用法: openxiangda permission role-update <roleCode|roleId> --json-file file');
|
|
2925
|
+
const target = getWorkspaceTarget(config, profileName, flags);
|
|
2926
|
+
const roleId = resolveRoleId(target.bound, roleCode, flags);
|
|
2927
|
+
const data = await runDirectRequest(config, target, flags, {
|
|
2928
|
+
method: 'PUT',
|
|
2929
|
+
path: `/openxiangda-api/v1/apps/${encodeURIComponent(target.appType)}/roles/${encodeURIComponent(roleId)}`,
|
|
2930
|
+
body: {
|
|
2931
|
+
code: body.code || roleCode,
|
|
2932
|
+
name: body.name || roleCode,
|
|
2933
|
+
description: body.description || '',
|
|
2934
|
+
},
|
|
2935
|
+
}, { returnData: true });
|
|
2936
|
+
if (!flags['dry-run']) {
|
|
2937
|
+
const resultCode = body.code || roleCode;
|
|
2938
|
+
if (data?.id) saveRoleResource(target, resultCode, data.id);
|
|
2939
|
+
if (flags['write-manifest']) writeDirectManifest('role', resultCode, { ...body, code: resultCode });
|
|
2940
|
+
}
|
|
2941
|
+
return outputDirectResult(data, flags);
|
|
2942
|
+
}
|
|
2943
|
+
|
|
2944
|
+
if (subcommand === 'role-delete') {
|
|
2945
|
+
const [roleKey] = positional;
|
|
2946
|
+
if (!roleKey || !flags.force) fail('用法: openxiangda permission role-delete <roleCode|roleId> --force');
|
|
2947
|
+
const target = getWorkspaceTarget(config, profileName, flags);
|
|
2948
|
+
const roleId = resolveRoleId(target.bound, roleKey, flags);
|
|
2949
|
+
const data = await runDirectRequest(config, target, flags, {
|
|
2950
|
+
method: 'DELETE',
|
|
2951
|
+
path: `/openxiangda-api/v1/apps/${encodeURIComponent(target.appType)}/roles/${encodeURIComponent(roleId)}`,
|
|
2952
|
+
}, { returnData: true });
|
|
2953
|
+
if (!flags['dry-run'] && target.bound.resources?.roles?.[roleKey]) {
|
|
2954
|
+
delete target.bound.resources.roles[roleKey];
|
|
2955
|
+
saveProjectState(target.state);
|
|
2956
|
+
}
|
|
2957
|
+
return outputDirectResult(data, flags);
|
|
2958
|
+
}
|
|
2959
|
+
|
|
2246
2960
|
if (subcommand === 'role-users') {
|
|
2247
2961
|
const [roleKey] = positional;
|
|
2248
2962
|
if (!roleKey) fail('用法: openxiangda permission role-users <roleCode|roleId>');
|
|
@@ -2380,6 +3094,42 @@ async function permission(args) {
|
|
|
2380
3094
|
return;
|
|
2381
3095
|
}
|
|
2382
3096
|
|
|
3097
|
+
if (subcommand === 'page-group-update') {
|
|
3098
|
+
const [groupKey] = positional;
|
|
3099
|
+
const body = readDirectJsonBody(flags, 'page permission group update');
|
|
3100
|
+
const groupCode = groupKey || body.code || body.resourceCode;
|
|
3101
|
+
if (!groupCode) fail('用法: openxiangda permission page-group-update <groupCode|groupId> --json-file file');
|
|
3102
|
+
const target = getWorkspaceTarget(config, profileName, flags);
|
|
3103
|
+
const groupId = flags['group-id'] || target.bound.resources?.pagePermissionGroups?.[groupCode]?.groupId || groupCode;
|
|
3104
|
+
const normalized = normalizePagePermissionGroupDirectBody(target.bound, { ...body, code: body.code || groupCode });
|
|
3105
|
+
const data = await runDirectRequest(config, target, flags, {
|
|
3106
|
+
method: 'PUT',
|
|
3107
|
+
path: `/openxiangda-api/v1/apps/${encodeURIComponent(target.appType)}/page-permission-groups/${encodeURIComponent(groupId)}`,
|
|
3108
|
+
body: normalized,
|
|
3109
|
+
}, { returnData: true });
|
|
3110
|
+
if (!flags['dry-run']) {
|
|
3111
|
+
if (data?.id) savePagePermissionGroupResource(target, body.code || groupCode, data.id, { name: data.name, resourceCode: body.code || groupCode });
|
|
3112
|
+
if (flags['write-manifest']) writeDirectManifest('page-permission-group', body.code || groupCode, { ...body, code: body.code || groupCode });
|
|
3113
|
+
}
|
|
3114
|
+
return outputDirectResult(data, flags);
|
|
3115
|
+
}
|
|
3116
|
+
|
|
3117
|
+
if (subcommand === 'page-group-delete') {
|
|
3118
|
+
const [groupKey] = positional;
|
|
3119
|
+
if (!groupKey || !flags.force) fail('用法: openxiangda permission page-group-delete <groupCode|groupId> --force');
|
|
3120
|
+
const target = getWorkspaceTarget(config, profileName, flags);
|
|
3121
|
+
const groupId = flags['group-id'] || target.bound.resources?.pagePermissionGroups?.[groupKey]?.groupId || groupKey;
|
|
3122
|
+
const data = await runDirectRequest(config, target, flags, {
|
|
3123
|
+
method: 'DELETE',
|
|
3124
|
+
path: `/openxiangda-api/v1/apps/${encodeURIComponent(target.appType)}/page-permission-groups/${encodeURIComponent(groupId)}`,
|
|
3125
|
+
}, { returnData: true });
|
|
3126
|
+
if (!flags['dry-run'] && target.bound.resources?.pagePermissionGroups?.[groupKey]) {
|
|
3127
|
+
delete target.bound.resources.pagePermissionGroups[groupKey];
|
|
3128
|
+
saveProjectState(target.state);
|
|
3129
|
+
}
|
|
3130
|
+
return outputDirectResult(data, flags);
|
|
3131
|
+
}
|
|
3132
|
+
|
|
2383
3133
|
if (subcommand === 'form-group-list') {
|
|
2384
3134
|
const target = getWorkspaceTarget(config, profileName, flags);
|
|
2385
3135
|
const formUuid = flags['form-uuid'] || resolveOptionalFormUuid(target.bound, flags['form-code']);
|
|
@@ -2477,6 +3227,44 @@ async function permission(args) {
|
|
|
2477
3227
|
return;
|
|
2478
3228
|
}
|
|
2479
3229
|
|
|
3230
|
+
if (subcommand === 'form-group-update') {
|
|
3231
|
+
const [groupKey] = positional;
|
|
3232
|
+
const body = readDirectJsonBody(flags, 'form permission group update');
|
|
3233
|
+
const groupCode = groupKey || body.code || body.resourceCode;
|
|
3234
|
+
const target = getWorkspaceTarget(config, profileName, flags);
|
|
3235
|
+
const formUuid = flags['form-uuid'] || resolveManifestFormUuid(target.bound, body);
|
|
3236
|
+
if (!groupCode || !formUuid) fail('用法: openxiangda permission form-group-update <groupCode|groupId> --form-code code|--form-uuid FORM --json-file file');
|
|
3237
|
+
const groupId = flags['group-id'] || target.bound.resources?.formPermissionGroups?.[groupCode]?.groupId || groupCode;
|
|
3238
|
+
const normalized = normalizeFormPermissionGroupManifest(target.bound, { ...body, code: body.code || groupCode, formUuid });
|
|
3239
|
+
const data = await runDirectRequest(config, target, flags, {
|
|
3240
|
+
method: 'PUT',
|
|
3241
|
+
path: `/openxiangda-api/v1/apps/${encodeURIComponent(target.appType)}/forms/${encodeURIComponent(formUuid)}/permission-groups/${encodeURIComponent(groupId)}`,
|
|
3242
|
+
body: normalized,
|
|
3243
|
+
}, { returnData: true });
|
|
3244
|
+
if (!flags['dry-run']) {
|
|
3245
|
+
if (data?.id) saveFormPermissionGroupResource(target, body.code || groupCode, data.id, { formUuid, name: data.name, resourceCode: body.code || groupCode });
|
|
3246
|
+
if (flags['write-manifest']) writeDirectManifest('form-permission-group', body.code || groupCode, { ...body, code: body.code || groupCode });
|
|
3247
|
+
}
|
|
3248
|
+
return outputDirectResult(data, flags);
|
|
3249
|
+
}
|
|
3250
|
+
|
|
3251
|
+
if (subcommand === 'form-group-delete') {
|
|
3252
|
+
const [groupKey] = positional;
|
|
3253
|
+
const target = getWorkspaceTarget(config, profileName, flags);
|
|
3254
|
+
const formUuid = flags['form-uuid'] || resolveOptionalFormUuid(target.bound, flags['form-code']);
|
|
3255
|
+
if (!groupKey || !formUuid || !flags.force) fail('用法: openxiangda permission form-group-delete <groupCode|groupId> --form-code code|--form-uuid FORM --force');
|
|
3256
|
+
const groupId = flags['group-id'] || target.bound.resources?.formPermissionGroups?.[groupKey]?.groupId || groupKey;
|
|
3257
|
+
const data = await runDirectRequest(config, target, flags, {
|
|
3258
|
+
method: 'DELETE',
|
|
3259
|
+
path: `/openxiangda-api/v1/apps/${encodeURIComponent(target.appType)}/forms/${encodeURIComponent(formUuid)}/permission-groups/${encodeURIComponent(groupId)}`,
|
|
3260
|
+
}, { returnData: true });
|
|
3261
|
+
if (!flags['dry-run'] && target.bound.resources?.formPermissionGroups?.[groupKey]) {
|
|
3262
|
+
delete target.bound.resources.formPermissionGroups[groupKey];
|
|
3263
|
+
saveProjectState(target.state);
|
|
3264
|
+
}
|
|
3265
|
+
return outputDirectResult(data, flags);
|
|
3266
|
+
}
|
|
3267
|
+
|
|
2480
3268
|
if (subcommand === 'form-summary') {
|
|
2481
3269
|
const target = getWorkspaceTarget(config, profileName, flags);
|
|
2482
3270
|
const formUuid = flags['form-uuid'] || resolveOptionalFormUuid(target.bound, flags['form-code'] || positional[0]);
|
|
@@ -2503,14 +3291,29 @@ async function permission(args) {
|
|
|
2503
3291
|
return;
|
|
2504
3292
|
}
|
|
2505
3293
|
|
|
3294
|
+
if (subcommand === 'audit') {
|
|
3295
|
+
const target = getWorkspaceTarget(config, profileName, flags);
|
|
3296
|
+
const result = await buildPermissionAudit(config, target, flags);
|
|
3297
|
+
if (flags.json) return writeJson(result);
|
|
3298
|
+
print(JSON.stringify(result, null, 2));
|
|
3299
|
+
if (result.errors.length > 0) fail(`permission audit 发现 ${result.errors.length} 个错误`);
|
|
3300
|
+
return;
|
|
3301
|
+
}
|
|
3302
|
+
|
|
2506
3303
|
fail(
|
|
2507
|
-
'用法: openxiangda permission role-list|role-create|role-bind|role-users|role-add-users|page-group-list|page-group-create|page-group-bind|form-group-list|form-group-create|form-group-bind|form-summary|menu-permissions'
|
|
3304
|
+
'用法: 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'
|
|
2508
3305
|
);
|
|
2509
3306
|
}
|
|
2510
3307
|
|
|
2511
3308
|
async function settings(args) {
|
|
2512
3309
|
const [subcommand, ...rest] = args;
|
|
2513
3310
|
const { flags, positional } = parseArgs(rest);
|
|
3311
|
+
if (wantsSubcommandHelp(subcommand, flags)) {
|
|
3312
|
+
print(
|
|
3313
|
+
'用法: openxiangda settings get|save|indexes|indexes-save|data-management|data-management-save|public-access|public-access-save|public-access-delete'
|
|
3314
|
+
);
|
|
3315
|
+
return;
|
|
3316
|
+
}
|
|
2514
3317
|
const config = loadConfig();
|
|
2515
3318
|
const profileName = flags.profile || config.currentProfile;
|
|
2516
3319
|
|
|
@@ -2673,16 +3476,25 @@ async function settings(args) {
|
|
|
2673
3476
|
|
|
2674
3477
|
async function resource(args) {
|
|
2675
3478
|
const { subcommand, rest } = parseSubcommandArgs(args);
|
|
2676
|
-
const { flags } = parseArgs(rest);
|
|
3479
|
+
const { flags, positional } = parseArgs(rest);
|
|
2677
3480
|
if (wantsSubcommandHelp(subcommand, flags)) {
|
|
2678
|
-
print('用法: openxiangda resource validate|plan|publish|pull|typegen [--profile name] [--json]');
|
|
3481
|
+
print('用法: openxiangda resource validate|plan|publish|pull|typegen|explain [type] [--profile name] [--json]');
|
|
2679
3482
|
return;
|
|
2680
3483
|
}
|
|
2681
3484
|
const config = loadConfig();
|
|
2682
3485
|
const profileName = flags.profile || config.currentProfile;
|
|
2683
3486
|
|
|
2684
|
-
if (!['validate', 'plan', 'publish', 'pull', 'typegen'].includes(subcommand)) {
|
|
2685
|
-
fail('用法: openxiangda resource validate|plan|publish|pull|typegen [--profile name] [--json]');
|
|
3487
|
+
if (!['validate', 'plan', 'publish', 'pull', 'typegen', 'explain'].includes(subcommand)) {
|
|
3488
|
+
fail('用法: openxiangda resource validate|plan|publish|pull|typegen|explain [--profile name] [--json]');
|
|
3489
|
+
}
|
|
3490
|
+
|
|
3491
|
+
if (subcommand === 'explain') {
|
|
3492
|
+
const type = positional[0] || flags.type || 'route';
|
|
3493
|
+
const result = getResourceExplain(type);
|
|
3494
|
+
if (!result) fail(`未知资源类型: ${type}`);
|
|
3495
|
+
if (flags.json) return writeJson({ type, ...result });
|
|
3496
|
+
print(renderResourceExplain(type));
|
|
3497
|
+
return;
|
|
2686
3498
|
}
|
|
2687
3499
|
|
|
2688
3500
|
if (subcommand === 'pull') {
|
|
@@ -3097,6 +3909,16 @@ function formatBytes(value) {
|
|
|
3097
3909
|
async function inspect(args) {
|
|
3098
3910
|
const [subcommand, ...rest] = args;
|
|
3099
3911
|
const { flags, positional } = parseArgs(rest);
|
|
3912
|
+
if (
|
|
3913
|
+
subcommand === 'help' ||
|
|
3914
|
+
subcommand === '--help' ||
|
|
3915
|
+
subcommand === '-h' ||
|
|
3916
|
+
flags.help ||
|
|
3917
|
+
flags.h
|
|
3918
|
+
) {
|
|
3919
|
+
print('用法: openxiangda inspect app|form|workflow|automation|permissions [--profile name] [--json]');
|
|
3920
|
+
return;
|
|
3921
|
+
}
|
|
3100
3922
|
const config = loadConfig();
|
|
3101
3923
|
const profileName = flags.profile || config.currentProfile;
|
|
3102
3924
|
|
|
@@ -3181,20 +4003,28 @@ async function commands(args) {
|
|
|
3181
4003
|
'update check|install',
|
|
3182
4004
|
'platform add|list|use|remove',
|
|
3183
4005
|
'auth status|refresh|logout',
|
|
4006
|
+
'doctor [--profile name] [--app-type APP_XXX]',
|
|
4007
|
+
'design gates|template [--topic code]',
|
|
3184
4008
|
'env',
|
|
3185
4009
|
'workspace init|bind|publish [--app-name] [--changed|--since|--form|--page|--only|--dry-run|--force|--resources|--skip-resources|--prune]',
|
|
3186
4010
|
'app list|create|snapshot',
|
|
3187
4011
|
'form list|create|bind|pull|publish',
|
|
3188
4012
|
'page list|publish|bind|releases|activate',
|
|
3189
|
-
'menu list|create|bind|delete',
|
|
4013
|
+
'menu list|create|update|sort|bind|delete',
|
|
3190
4014
|
'workflow list|create|bind|pull|publish|delete|validate',
|
|
3191
4015
|
'automation list|create|bind|pull|publish|unpublish|enable|disable|delete|validate|cron-validate',
|
|
3192
|
-
'data-view list|status|refresh|query|stats',
|
|
3193
|
-
'
|
|
3194
|
-
'
|
|
3195
|
-
'
|
|
4016
|
+
'data-view list|get|create|update|upsert|delete|status|refresh|query|stats',
|
|
4017
|
+
'route list|get|create|update|upsert|delete',
|
|
4018
|
+
'public-access list|get|create|update|upsert|delete|ticket-create|session-test|grant-check',
|
|
4019
|
+
'auth-config list|get|create|update|upsert|delete|methods',
|
|
4020
|
+
'function list|get|create|update|upsert|delete|invoke',
|
|
4021
|
+
'connector list|get|create|update|upsert|delete|invoke|download-test',
|
|
4022
|
+
'notification template-list|template-get|template-upsert|template-delete|type-list|type-get|type-upsert|type-delete|preview|send|batch-send',
|
|
4023
|
+
'permission role-list|role-create|role-update|role-delete|role-bind|role-users|role-add-users|audit',
|
|
4024
|
+
'permission page-group-list|page-group-create|page-group-update|page-group-delete|page-group-bind',
|
|
4025
|
+
'permission form-group-list|form-group-create|form-group-update|form-group-delete|form-group-bind|form-summary|menu-permissions',
|
|
3196
4026
|
'settings get|save|indexes|indexes-save|data-management|data-management-save|public-access|public-access-save|public-access-delete',
|
|
3197
|
-
'resource validate|plan|publish|pull|typegen',
|
|
4027
|
+
'resource validate|plan|publish|pull|typegen|explain',
|
|
3198
4028
|
'inspect app|form|workflow|automation|permissions',
|
|
3199
4029
|
'feedback preview|submit',
|
|
3200
4030
|
'skill install|status|bootstrap',
|
|
@@ -3228,6 +4058,10 @@ function printWorkspaceInitReport(result) {
|
|
|
3228
4058
|
async function skill(args) {
|
|
3229
4059
|
const [subcommand, ...rest] = args;
|
|
3230
4060
|
const { flags, positional } = parseArgs(rest);
|
|
4061
|
+
if (wantsSubcommandHelp(subcommand, flags)) {
|
|
4062
|
+
print('用法: openxiangda skill install|status [--agent codex|claude|qoder|dual] [--dest <skills-dir>] [--force] [--dry-run] [--json]\n openxiangda skill bootstrap [<dir>] [--force] [--dry-run] [--json]');
|
|
4063
|
+
return;
|
|
4064
|
+
}
|
|
3231
4065
|
const options = {
|
|
3232
4066
|
agent: flags.agent || 'codex',
|
|
3233
4067
|
dest: flags.dest,
|
|
@@ -3365,6 +4199,334 @@ function getWorkspaceTarget(config, profileName, flags = {}) {
|
|
|
3365
4199
|
};
|
|
3366
4200
|
}
|
|
3367
4201
|
|
|
4202
|
+
async function directResourceCrud(config, target, spec) {
|
|
4203
|
+
const { subcommand, flags, positional } = spec;
|
|
4204
|
+
const basePath = spec.basePath;
|
|
4205
|
+
|
|
4206
|
+
if (subcommand === 'list') {
|
|
4207
|
+
return runDirectRequest(config, target, flags, {
|
|
4208
|
+
method: 'GET',
|
|
4209
|
+
path: apiPathWithQuery(basePath, {
|
|
4210
|
+
page: flags.page,
|
|
4211
|
+
pageSize: flags['page-size'] || flags.limit,
|
|
4212
|
+
limit: flags.limit,
|
|
4213
|
+
code: flags.code,
|
|
4214
|
+
}),
|
|
4215
|
+
});
|
|
4216
|
+
}
|
|
4217
|
+
|
|
4218
|
+
if (subcommand === 'get') {
|
|
4219
|
+
const code = positional[0] || flags.code;
|
|
4220
|
+
if (!code) fail(`用法: openxiangda ${spec.commandName} get <code>`);
|
|
4221
|
+
return runDirectRequest(config, target, flags, {
|
|
4222
|
+
method: 'GET',
|
|
4223
|
+
path: `${basePath}/${encodeURIComponent(code)}`,
|
|
4224
|
+
});
|
|
4225
|
+
}
|
|
4226
|
+
|
|
4227
|
+
if (subcommand === 'delete') {
|
|
4228
|
+
const code = positional[0] || flags.code;
|
|
4229
|
+
if (!code || !flags.force) fail(`用法: openxiangda ${spec.commandName} delete <code> --force`);
|
|
4230
|
+
const data = await runDirectRequest(config, target, flags, {
|
|
4231
|
+
method: 'DELETE',
|
|
4232
|
+
path: `${basePath}/${encodeURIComponent(code)}`,
|
|
4233
|
+
}, { returnData: true });
|
|
4234
|
+
if (!flags['dry-run']) removeDirectStateResource(target, spec.resourceType, code);
|
|
4235
|
+
return outputDirectResult(data, flags);
|
|
4236
|
+
}
|
|
4237
|
+
|
|
4238
|
+
if (!['create', 'update', 'upsert'].includes(subcommand)) {
|
|
4239
|
+
fail(`用法: openxiangda ${spec.commandName} list|get|create|update|upsert|delete`);
|
|
4240
|
+
}
|
|
4241
|
+
|
|
4242
|
+
const rawBody = readDirectJsonBody(flags, spec.commandName);
|
|
4243
|
+
const explicitCode = positional[0] || flags.code;
|
|
4244
|
+
if (explicitCode && !rawBody.code && !rawBody.resourceCode && !rawBody.functionCode && !rawBody.methodName) {
|
|
4245
|
+
rawBody.code = explicitCode;
|
|
4246
|
+
}
|
|
4247
|
+
const code = explicitCode || spec.codeOf(rawBody);
|
|
4248
|
+
if (!code) fail(`openxiangda ${spec.commandName} ${subcommand} 缺少资源 code`);
|
|
4249
|
+
const normalizedBody = spec.prepareBody
|
|
4250
|
+
? await spec.prepareBody(rawBody)
|
|
4251
|
+
: spec.normalizeBody
|
|
4252
|
+
? spec.normalizeBody(rawBody)
|
|
4253
|
+
: rawBody;
|
|
4254
|
+
|
|
4255
|
+
let method = subcommand === 'create' ? (spec.createMethod || 'POST') : (spec.updateMethod || 'PUT');
|
|
4256
|
+
let requestPath = subcommand === 'create'
|
|
4257
|
+
? (spec.createPath || basePath)
|
|
4258
|
+
: (spec.updatePath || `${basePath}/${encodeURIComponent(code)}`);
|
|
4259
|
+
let requestBody = subcommand === 'create'
|
|
4260
|
+
? (spec.createBody ? spec.createBody(rawBody, normalizedBody) : normalizedBody)
|
|
4261
|
+
: (spec.updateBody ? spec.updateBody(rawBody, normalizedBody) : normalizedBody);
|
|
4262
|
+
let action = subcommand;
|
|
4263
|
+
|
|
4264
|
+
if (subcommand === 'upsert') {
|
|
4265
|
+
if (flags['dry-run']) {
|
|
4266
|
+
return outputDirectResult({
|
|
4267
|
+
dryRun: true,
|
|
4268
|
+
action: 'upsert',
|
|
4269
|
+
existsCheck: { method: 'GET', path: `${basePath}/${encodeURIComponent(code)}` },
|
|
4270
|
+
create: {
|
|
4271
|
+
method: spec.createMethod || 'POST',
|
|
4272
|
+
path: spec.createPath || basePath,
|
|
4273
|
+
body: spec.createBody ? spec.createBody(rawBody, normalizedBody) : normalizedBody,
|
|
4274
|
+
},
|
|
4275
|
+
update: {
|
|
4276
|
+
method: spec.updateMethod || 'PUT',
|
|
4277
|
+
path: spec.updatePath || `${basePath}/${encodeURIComponent(code)}`,
|
|
4278
|
+
body: spec.updateBody ? spec.updateBody(rawBody, normalizedBody) : normalizedBody,
|
|
4279
|
+
},
|
|
4280
|
+
}, flags);
|
|
4281
|
+
}
|
|
4282
|
+
const existing = await requestOptionalWithAuth(
|
|
4283
|
+
config,
|
|
4284
|
+
target.profileName,
|
|
4285
|
+
`${basePath}/${encodeURIComponent(code)}`
|
|
4286
|
+
);
|
|
4287
|
+
action = existing ? 'update' : 'create';
|
|
4288
|
+
method = existing ? (spec.updateMethod || 'PUT') : (spec.createMethod || 'POST');
|
|
4289
|
+
requestPath = existing
|
|
4290
|
+
? (spec.updatePath || `${basePath}/${encodeURIComponent(code)}`)
|
|
4291
|
+
: (spec.createPath || basePath);
|
|
4292
|
+
requestBody = existing
|
|
4293
|
+
? (spec.updateBody ? spec.updateBody(rawBody, normalizedBody) : normalizedBody)
|
|
4294
|
+
: (spec.createBody ? spec.createBody(rawBody, normalizedBody) : normalizedBody);
|
|
4295
|
+
}
|
|
4296
|
+
|
|
4297
|
+
const response = await runDirectRequest(config, target, flags, {
|
|
4298
|
+
method,
|
|
4299
|
+
path: requestPath,
|
|
4300
|
+
body: requestBody,
|
|
4301
|
+
}, { returnData: true });
|
|
4302
|
+
const data = spec.responseData ? spec.responseData(response) : response;
|
|
4303
|
+
if (!flags['dry-run']) {
|
|
4304
|
+
if (spec.saveState) spec.saveState(code, data);
|
|
4305
|
+
if (flags['write-manifest']) writeDirectManifest(spec.resourceType, code, { ...rawBody, code });
|
|
4306
|
+
}
|
|
4307
|
+
return outputDirectResult({ action, data }, flags);
|
|
4308
|
+
}
|
|
4309
|
+
|
|
4310
|
+
async function runDirectRequest(config, target, flags, request, options = {}) {
|
|
4311
|
+
const plan = {
|
|
4312
|
+
dryRun: true,
|
|
4313
|
+
method: request.method || 'GET',
|
|
4314
|
+
path: request.path,
|
|
4315
|
+
body: request.body,
|
|
4316
|
+
};
|
|
4317
|
+
if (flags['dry-run']) {
|
|
4318
|
+
if (options.returnData) return plan;
|
|
4319
|
+
return outputDirectResult(plan, flags);
|
|
4320
|
+
}
|
|
4321
|
+
|
|
4322
|
+
const requestOptions = {
|
|
4323
|
+
method: request.method || 'GET',
|
|
4324
|
+
body: request.body,
|
|
4325
|
+
strictEnvelope: request.strictEnvelope,
|
|
4326
|
+
};
|
|
4327
|
+
let data;
|
|
4328
|
+
if (request.auth === false) {
|
|
4329
|
+
const payload = await requestJson(target.profile.baseUrl, request.path, requestOptions);
|
|
4330
|
+
data = request.strictEnvelope ? unwrapStrictApi(payload) : unwrapApi(payload);
|
|
4331
|
+
} else {
|
|
4332
|
+
data = await requestWithAuth(config, target.profileName, request.path, requestOptions);
|
|
4333
|
+
}
|
|
4334
|
+
if (options.returnData) return data;
|
|
4335
|
+
return outputDirectResult(data, flags);
|
|
4336
|
+
}
|
|
4337
|
+
|
|
4338
|
+
function outputDirectResult(data, flags = {}) {
|
|
4339
|
+
if (flags.json) return writeJson(data);
|
|
4340
|
+
print(JSON.stringify(data, null, 2));
|
|
4341
|
+
}
|
|
4342
|
+
|
|
4343
|
+
function readDirectJsonBody(flags = {}, label, options = {}) {
|
|
4344
|
+
if (flags['json-file']) return readJsonArg(flags['json-file'], 'json-file');
|
|
4345
|
+
if (flags['body-json']) return readJsonArg(flags['body-json'], 'body-json');
|
|
4346
|
+
if (options.optional) return {};
|
|
4347
|
+
fail(`${label} 缺少 --json-file <file> 或 --body-json <json|file>`);
|
|
4348
|
+
}
|
|
4349
|
+
|
|
4350
|
+
function writeDirectManifest(resourceType, code, value) {
|
|
4351
|
+
const dir = directManifestDir(resourceType);
|
|
4352
|
+
if (!dir) return;
|
|
4353
|
+
const fileName = `${String(code).replace(/[^A-Za-z0-9_.-]+/g, '_')}.json`;
|
|
4354
|
+
writeResourceJsonFile(path.join(process.cwd(), dir, fileName), value);
|
|
4355
|
+
}
|
|
4356
|
+
|
|
4357
|
+
function directManifestDir(resourceType) {
|
|
4358
|
+
const dirs = {
|
|
4359
|
+
route: path.join('src', 'resources', 'routes'),
|
|
4360
|
+
'public-access': path.join('src', 'resources', 'public-access'),
|
|
4361
|
+
'auth-config': path.join('src', 'resources', 'auth'),
|
|
4362
|
+
function: path.join('src', 'resources', 'functions'),
|
|
4363
|
+
connector: path.join('src', 'resources', 'connectors'),
|
|
4364
|
+
notification: path.join('src', 'resources', 'notifications'),
|
|
4365
|
+
'data-view': path.join('src', 'resources', 'data-views'),
|
|
4366
|
+
menu: path.join('src', 'resources', 'menus'),
|
|
4367
|
+
role: path.join('src', 'resources', 'roles'),
|
|
4368
|
+
'page-permission-group': path.join('src', 'resources', 'permissions', 'page-groups'),
|
|
4369
|
+
'form-permission-group': path.join('src', 'resources', 'permissions', 'form-groups'),
|
|
4370
|
+
};
|
|
4371
|
+
return dirs[resourceType];
|
|
4372
|
+
}
|
|
4373
|
+
|
|
4374
|
+
function removeDirectStateResource(target, resourceType, code) {
|
|
4375
|
+
const buckets = {
|
|
4376
|
+
route: 'routes',
|
|
4377
|
+
'public-access': 'publicAccessPolicies',
|
|
4378
|
+
'auth-config': 'authConfigs',
|
|
4379
|
+
function: 'functions',
|
|
4380
|
+
connector: 'connectors',
|
|
4381
|
+
'data-view': 'dataViews',
|
|
4382
|
+
};
|
|
4383
|
+
const bucket = buckets[resourceType];
|
|
4384
|
+
if (!bucket || !target.bound.resources?.[bucket]?.[code]) return;
|
|
4385
|
+
delete target.bound.resources[bucket][code];
|
|
4386
|
+
saveProjectState(target.state);
|
|
4387
|
+
}
|
|
4388
|
+
|
|
4389
|
+
function normalizeMenuDirectBody(bound, menuItem) {
|
|
4390
|
+
const formUuid = resolveManifestFormUuid(bound, menuItem);
|
|
4391
|
+
const pageId = resolveManifestPageId(bound, menuItem);
|
|
4392
|
+
const parentId = resolveManifestMenuId(bound, menuItem.parentCode) || menuItem.parentId || null;
|
|
4393
|
+
return stripUndefinedValues({
|
|
4394
|
+
resourceCode: menuItem.code || menuItem.resourceCode,
|
|
4395
|
+
name: menuItem.name || menuItem.code,
|
|
4396
|
+
type: menuItem.type || 'nav',
|
|
4397
|
+
formUuid,
|
|
4398
|
+
pageId,
|
|
4399
|
+
parentId,
|
|
4400
|
+
sortOrder: menuItem.sortOrder,
|
|
4401
|
+
icon: menuItem.icon || null,
|
|
4402
|
+
isHidden: menuItem.isHidden,
|
|
4403
|
+
routeCode: menuItem.routeCode || null,
|
|
4404
|
+
path: menuItem.path || null,
|
|
4405
|
+
});
|
|
4406
|
+
}
|
|
4407
|
+
|
|
4408
|
+
function normalizePagePermissionGroupDirectBody(bound, group) {
|
|
4409
|
+
return stripUndefinedValues({
|
|
4410
|
+
resourceCode: group.code || group.resourceCode,
|
|
4411
|
+
name: group.name || group.code,
|
|
4412
|
+
roles: group.roles || [],
|
|
4413
|
+
menuFormUuids: resolvePagePermissionGroupTargets(bound, group),
|
|
4414
|
+
menuCodes: normalizePermissionCodeArray(group.menuCodes),
|
|
4415
|
+
routeCodes: normalizePermissionCodeArray(group.routeCodes),
|
|
4416
|
+
pathPatterns: normalizePermissionCodeArray(group.pathPatterns),
|
|
4417
|
+
});
|
|
4418
|
+
}
|
|
4419
|
+
|
|
4420
|
+
function parseConnectorApiName(targetName, flags = {}) {
|
|
4421
|
+
const raw = String(targetName || '');
|
|
4422
|
+
const separator = raw.indexOf('.');
|
|
4423
|
+
const connectorCode = separator >= 0 ? raw.slice(0, separator) : raw;
|
|
4424
|
+
const apiCode = flags.api || flags['api-code'] || (separator >= 0 ? raw.slice(separator + 1) : undefined);
|
|
4425
|
+
if (!connectorCode || !apiCode) fail('连接器调用需要 <connectorCode.apiCode> 或 --api <apiCode>');
|
|
4426
|
+
return { connector: connectorCode, api: apiCode };
|
|
4427
|
+
}
|
|
4428
|
+
|
|
4429
|
+
function extractNotificationTemplateBody(bound, body) {
|
|
4430
|
+
const template = Array.isArray(body.templates) ? body.templates[0] : body.template || body;
|
|
4431
|
+
return {
|
|
4432
|
+
...template,
|
|
4433
|
+
code: template.code || template.templateCode || body.code || body.templateCode,
|
|
4434
|
+
formCode: template.formCode || body.formCode,
|
|
4435
|
+
formUuid: template.formUuid || body.formUuid,
|
|
4436
|
+
level: template.level || body.level,
|
|
4437
|
+
};
|
|
4438
|
+
}
|
|
4439
|
+
|
|
4440
|
+
function extractNotificationTypeConfigBody(bound, body) {
|
|
4441
|
+
const config = Array.isArray(body.typeConfigs)
|
|
4442
|
+
? body.typeConfigs[0]
|
|
4443
|
+
: Array.isArray(body.notificationTypeConfigs)
|
|
4444
|
+
? body.notificationTypeConfigs[0]
|
|
4445
|
+
: body.typeConfig || body;
|
|
4446
|
+
return {
|
|
4447
|
+
...config,
|
|
4448
|
+
notificationType: config.notificationType || body.notificationType || body.code,
|
|
4449
|
+
formCode: config.formCode || body.formCode,
|
|
4450
|
+
formUuid: config.formUuid || body.formUuid,
|
|
4451
|
+
level: config.level || body.level,
|
|
4452
|
+
};
|
|
4453
|
+
}
|
|
4454
|
+
|
|
4455
|
+
function buildPublicGrantChecks(bound, grants, flags = {}) {
|
|
4456
|
+
const checks = [];
|
|
4457
|
+
const formTargets = [
|
|
4458
|
+
...splitList(flags['form-code']).map(code => resolveOptionalFormUuid(bound, code)),
|
|
4459
|
+
...splitList(flags['form-uuid']),
|
|
4460
|
+
].filter(Boolean);
|
|
4461
|
+
for (const formUuid of formTargets) {
|
|
4462
|
+
checks.push({ type: 'form', code: formUuid, allowed: (grants.forms || []).includes(formUuid) });
|
|
4463
|
+
}
|
|
4464
|
+
for (const code of splitList(flags['data-view'])) {
|
|
4465
|
+
checks.push({ type: 'dataView', code, allowed: (grants.dataViews || []).includes(code) });
|
|
4466
|
+
}
|
|
4467
|
+
for (const code of splitList(flags.function || flags['function-code'])) {
|
|
4468
|
+
checks.push({ type: 'function', code, allowed: (grants.functions || []).includes(code) });
|
|
4469
|
+
}
|
|
4470
|
+
for (const code of splitList(flags.connector || flags['connector-code'])) {
|
|
4471
|
+
checks.push({ type: 'connector', code, allowed: (grants.connectors || []).includes(code) });
|
|
4472
|
+
}
|
|
4473
|
+
if (checks.length === 0) {
|
|
4474
|
+
return [
|
|
4475
|
+
{ type: 'forms', granted: grants.forms || [], allowed: true },
|
|
4476
|
+
{ type: 'dataViews', granted: grants.dataViews || [], allowed: true },
|
|
4477
|
+
{ type: 'functions', granted: grants.functions || [], allowed: true },
|
|
4478
|
+
{ type: 'connectors', granted: grants.connectors || [], allowed: true },
|
|
4479
|
+
];
|
|
4480
|
+
}
|
|
4481
|
+
return checks;
|
|
4482
|
+
}
|
|
4483
|
+
|
|
4484
|
+
async function buildPermissionAudit(config, target, flags = {}) {
|
|
4485
|
+
const manifest = loadWorkspaceResources();
|
|
4486
|
+
const validation = validateWorkspaceResources(manifest);
|
|
4487
|
+
const roleCodes = new Set((manifest.roles || []).map(item => item.code || item.resourceCode).filter(Boolean));
|
|
4488
|
+
const warnings = [...validation.warnings];
|
|
4489
|
+
const errors = [...validation.errors];
|
|
4490
|
+
for (const group of manifest.pagePermissionGroups || []) {
|
|
4491
|
+
for (const roleCode of group.roles || []) {
|
|
4492
|
+
if (roleCodes.size > 0 && !roleCodes.has(roleCode)) warnings.push(`page permission group ${group.code} 引用未声明角色: ${roleCode}`);
|
|
4493
|
+
}
|
|
4494
|
+
}
|
|
4495
|
+
for (const group of manifest.formPermissionGroups || []) {
|
|
4496
|
+
for (const roleCode of group.roles || []) {
|
|
4497
|
+
if (roleCodes.size > 0 && !roleCodes.has(roleCode)) warnings.push(`form permission group ${group.code} 引用未声明角色: ${roleCode}`);
|
|
4498
|
+
}
|
|
4499
|
+
const formUuid = resolveManifestFormUuid(target.bound, group);
|
|
4500
|
+
if (!formUuid) errors.push(`form permission group ${group.code} 缺少 formCode/formUuid`);
|
|
4501
|
+
}
|
|
4502
|
+
for (const policy of manifest.publicAccessPolicies || []) {
|
|
4503
|
+
const grants = normalizePublicAccessPolicyManifest(target.bound, policy).grants || {};
|
|
4504
|
+
if (
|
|
4505
|
+
(grants.forms || []).length === 0 &&
|
|
4506
|
+
(grants.dataViews || []).length === 0 &&
|
|
4507
|
+
(grants.functions || []).length === 0 &&
|
|
4508
|
+
(grants.connectors || []).length === 0
|
|
4509
|
+
) {
|
|
4510
|
+
warnings.push(`public-access policy ${policy.code} 未声明任何 grants`);
|
|
4511
|
+
}
|
|
4512
|
+
}
|
|
4513
|
+
const result = {
|
|
4514
|
+
appType: target.appType,
|
|
4515
|
+
manifestCounts: Object.fromEntries(
|
|
4516
|
+
RESOURCE_SPECS.map(spec => [spec.key, (manifest[spec.key] || []).length])
|
|
4517
|
+
),
|
|
4518
|
+
errors,
|
|
4519
|
+
warnings,
|
|
4520
|
+
};
|
|
4521
|
+
if (flags.live) {
|
|
4522
|
+
result.live = {
|
|
4523
|
+
roles: await requestWithAuth(config, target.profileName, `/openxiangda-api/v1/apps/${encodeURIComponent(target.appType)}/roles`),
|
|
4524
|
+
pagePermissionGroups: await requestWithAuth(config, target.profileName, `/openxiangda-api/v1/apps/${encodeURIComponent(target.appType)}/page-permission-groups`),
|
|
4525
|
+
};
|
|
4526
|
+
}
|
|
4527
|
+
return result;
|
|
4528
|
+
}
|
|
4529
|
+
|
|
3368
4530
|
function ensureResourceBuckets(bound) {
|
|
3369
4531
|
bound.resources = bound.resources || {};
|
|
3370
4532
|
bound.resources.forms = bound.resources.forms || {};
|
|
@@ -7884,6 +9046,31 @@ function unwrapApi(payload) {
|
|
|
7884
9046
|
return 'data' in payload ? payload.data : payload;
|
|
7885
9047
|
}
|
|
7886
9048
|
|
|
9049
|
+
function unwrapStrictApi(payload) {
|
|
9050
|
+
if (!payload || typeof payload !== 'object') return payload;
|
|
9051
|
+
if (payload.success === false) {
|
|
9052
|
+
const error = new Error(payload.message || payload.errorMessage || 'OpenXiangda API request failed');
|
|
9053
|
+
error.apiCode = payload.code;
|
|
9054
|
+
throw error;
|
|
9055
|
+
}
|
|
9056
|
+
if ('code' in payload) {
|
|
9057
|
+
const code = payload.code;
|
|
9058
|
+
const numericCode = Number(code);
|
|
9059
|
+
const normalizedCode = String(code || '').toUpperCase();
|
|
9060
|
+
const isOk =
|
|
9061
|
+
numericCode === 0 ||
|
|
9062
|
+
(Number.isFinite(numericCode) && numericCode >= 200 && numericCode < 300) ||
|
|
9063
|
+
normalizedCode === 'OK' ||
|
|
9064
|
+
normalizedCode === 'SUCCESS';
|
|
9065
|
+
if (!isOk) {
|
|
9066
|
+
const error = new Error(payload.message || payload.errorMessage || String(code));
|
|
9067
|
+
error.apiCode = code;
|
|
9068
|
+
throw error;
|
|
9069
|
+
}
|
|
9070
|
+
}
|
|
9071
|
+
return 'data' in payload ? payload.data : payload;
|
|
9072
|
+
}
|
|
9073
|
+
|
|
7887
9074
|
function isUnauthorized(error) {
|
|
7888
9075
|
return (
|
|
7889
9076
|
Number(error?.apiCode) === 401 ||
|
|
@@ -7897,23 +9084,24 @@ async function requestWithAuth(config, profileName, apiPath, options = {}) {
|
|
|
7897
9084
|
if (!profile.token?.accessToken) {
|
|
7898
9085
|
fail(`profile ${resolved.profileName} 未登录,请先执行 openxiangda login --profile ${resolved.profileName}`);
|
|
7899
9086
|
}
|
|
9087
|
+
const { strictEnvelope, ...requestOptions } = options;
|
|
7900
9088
|
|
|
7901
9089
|
try {
|
|
7902
9090
|
const payload = await requestJson(profile.baseUrl, apiPath, {
|
|
7903
|
-
...
|
|
9091
|
+
...requestOptions,
|
|
7904
9092
|
accessToken: profile.token.accessToken,
|
|
7905
9093
|
});
|
|
7906
|
-
return unwrapApi(payload);
|
|
9094
|
+
return strictEnvelope ? unwrapStrictApi(payload) : unwrapApi(payload);
|
|
7907
9095
|
} catch (error) {
|
|
7908
9096
|
if (!isUnauthorized(error) || !profile.token?.refreshToken) {
|
|
7909
9097
|
throw error;
|
|
7910
9098
|
}
|
|
7911
9099
|
await refreshProfile(config, resolved.profileName);
|
|
7912
9100
|
const payload = await requestJson(profile.baseUrl, apiPath, {
|
|
7913
|
-
...
|
|
9101
|
+
...requestOptions,
|
|
7914
9102
|
accessToken: profile.token.accessToken,
|
|
7915
9103
|
});
|
|
7916
|
-
return unwrapApi(payload);
|
|
9104
|
+
return strictEnvelope ? unwrapStrictApi(payload) : unwrapApi(payload);
|
|
7917
9105
|
}
|
|
7918
9106
|
}
|
|
7919
9107
|
|