openxiangda 1.0.0
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 +58 -0
- package/bin/openxiangda.js +11 -0
- package/lib/cli.js +2423 -0
- package/lib/config.js +121 -0
- package/lib/http.js +47 -0
- package/lib/skills.js +371 -0
- package/lib/utils.js +87 -0
- package/lib/workspace-init.js +139 -0
- package/openxiangda-skills/SKILL.md +128 -0
- package/openxiangda-skills/references/architecture-patterns.md +242 -0
- package/openxiangda-skills/references/automation-v3.md +129 -0
- package/openxiangda-skills/references/component-guide.md +198 -0
- package/openxiangda-skills/references/forms/component-registry.md +53 -0
- package/openxiangda-skills/references/forms/form-schema.md +109 -0
- package/openxiangda-skills/references/forms/layout-and-rules.md +24 -0
- package/openxiangda-skills/references/openxiangda-api.md +466 -0
- package/openxiangda-skills/references/pages/page-sdk.md +13 -0
- package/openxiangda-skills/references/pages/publish-flow.md +36 -0
- package/openxiangda-skills/references/pages/workspace-structure.md +38 -0
- package/openxiangda-skills/references/permissions-settings.md +147 -0
- package/openxiangda-skills/references/platform-data-model.md +305 -0
- package/openxiangda-skills/references/style-system.md +492 -0
- package/openxiangda-skills/references/troubleshooting.md +246 -0
- package/openxiangda-skills/references/workflow-v3.md +105 -0
- package/openxiangda-skills/references/workspace-state.md +45 -0
- package/openxiangda-skills/skills/openxiangda-app/SKILL.md +64 -0
- package/openxiangda-skills/skills/openxiangda-core/SKILL.md +143 -0
- package/openxiangda-skills/skills/openxiangda-form/SKILL.md +76 -0
- package/openxiangda-skills/skills/openxiangda-inspect/SKILL.md +40 -0
- package/openxiangda-skills/skills/openxiangda-page/SKILL.md +62 -0
- package/openxiangda-skills/skills/openxiangda-permission-settings/SKILL.md +95 -0
- package/openxiangda-skills/skills/openxiangda-workflow-automation/SKILL.md +97 -0
- package/package.json +126 -0
- package/packages/sdk/bin/lowcode-workspace.mjs +4 -0
- package/packages/sdk/dist/build/index.cjs +33 -0
- package/packages/sdk/dist/build/index.cjs.map +1 -0
- package/packages/sdk/dist/build/index.d.mts +40 -0
- package/packages/sdk/dist/build/index.d.ts +40 -0
- package/packages/sdk/dist/build/index.mjs +8 -0
- package/packages/sdk/dist/build/index.mjs.map +1 -0
- package/packages/sdk/dist/components/index.cjs +18700 -0
- package/packages/sdk/dist/components/index.cjs.map +1 -0
- package/packages/sdk/dist/components/index.d.mts +2094 -0
- package/packages/sdk/dist/components/index.d.ts +2094 -0
- package/packages/sdk/dist/components/index.mjs +18649 -0
- package/packages/sdk/dist/components/index.mjs.map +1 -0
- package/packages/sdk/dist/runtime/index.cjs +1469 -0
- package/packages/sdk/dist/runtime/index.cjs.map +1 -0
- package/packages/sdk/dist/runtime/index.d.mts +831 -0
- package/packages/sdk/dist/runtime/index.d.ts +831 -0
- package/packages/sdk/dist/runtime/index.mjs +1420 -0
- package/packages/sdk/dist/runtime/index.mjs.map +1 -0
- package/packages/sdk/dist/styles/antd-theme.cjs +60 -0
- package/packages/sdk/dist/styles/antd-theme.cjs.map +1 -0
- package/packages/sdk/dist/styles/antd-theme.d.mts +5 -0
- package/packages/sdk/dist/styles/antd-theme.d.ts +5 -0
- package/packages/sdk/dist/styles/antd-theme.mjs +35 -0
- package/packages/sdk/dist/styles/antd-theme.mjs.map +1 -0
- package/packages/sdk/dist/styles/tailwind-preset.cjs +2641 -0
- package/packages/sdk/dist/styles/tailwind-preset.cjs.map +1 -0
- package/packages/sdk/dist/styles/tailwind-preset.d.mts +75 -0
- package/packages/sdk/dist/styles/tailwind-preset.d.ts +75 -0
- package/packages/sdk/dist/styles/tailwind-preset.mjs +2618 -0
- package/packages/sdk/dist/styles/tailwind-preset.mjs.map +1 -0
- package/packages/sdk/dist/styles/tokens.css +73 -0
- package/packages/sdk/src/build-source/README.md +9 -0
- package/packages/sdk/src/build-source/bin/lowcode-workspace.mjs +7 -0
- package/packages/sdk/src/build-source/package.json +34 -0
- package/packages/sdk/src/build-source/scripts/build-forms.mjs +824 -0
- package/packages/sdk/src/build-source/scripts/build-forms.runtime-entry.test.ts +18 -0
- package/packages/sdk/src/build-source/scripts/build-pages.mjs +793 -0
- package/packages/sdk/src/build-source/scripts/build-workspace.mjs +64 -0
- package/packages/sdk/src/build-source/scripts/publish-all.mjs +127 -0
- package/packages/sdk/src/build-source/scripts/publish-oss.mjs +149 -0
- package/packages/sdk/src/build-source/scripts/register-bundle.mjs +1 -0
- package/packages/sdk/src/build-source/scripts/register.mjs +329 -0
- package/packages/sdk/src/build-source/scripts/sync-schema.mjs +301 -0
- package/packages/sdk/src/build-source/scripts/utils/form-api.mjs +639 -0
- package/packages/sdk/src/build-source/scripts/utils/form-api.test.ts +244 -0
- package/packages/sdk/src/build-source/scripts/utils/form-runtime-assets.mjs +57 -0
- package/packages/sdk/src/build-source/scripts/utils/form-runtime-assets.test.ts +135 -0
- package/packages/sdk/src/build-source/scripts/utils/incremental.mjs +210 -0
- package/packages/sdk/src/build-source/scripts/utils/load-config.mjs +257 -0
- package/packages/sdk/src/build-source/scripts/utils/load-config.test.ts +44 -0
- package/packages/sdk/src/build-source/scripts/utils/mime-types.mjs +70 -0
- package/packages/sdk/src/build-source/scripts/utils/namespace-css.mjs +61 -0
- package/packages/sdk/src/build-source/scripts/utils/oss-client.mjs +128 -0
- package/packages/sdk/src/build-source/scripts/utils/pages.mjs +80 -0
- package/packages/sdk/src/build-source/scripts/utils/progress.mjs +57 -0
- package/packages/sdk/src/build-source/scripts/utils/register-payload.mjs +89 -0
- package/packages/sdk/src/build-source/scripts/utils/register-payload.test.ts +76 -0
- package/packages/sdk/src/build-source/scripts/utils/runtime-css-check.mjs +44 -0
- package/packages/sdk/src/build-source/scripts/utils/runtime-css-check.test.ts +54 -0
- package/packages/sdk/src/build-source/scripts/utils/schema-transform.mjs +130 -0
- package/packages/sdk/src/build-source/scripts/utils/schema-transform.test.ts +141 -0
- package/packages/sdk/src/build-source/scripts/utils/tailwind-config.mjs +227 -0
- package/packages/sdk/src/build-source/scripts/utils/tailwind-config.test.ts +187 -0
- package/packages/sdk/src/build-source/src/cli.mjs +679 -0
- package/templates/sy-lowcode-app-workspace/app-workspace.config.ts +34 -0
- package/templates/sy-lowcode-app-workspace/examples/forms/customer/page.tsx +1 -0
- package/templates/sy-lowcode-app-workspace/examples/forms/customer/schema.ts +35 -0
- package/templates/sy-lowcode-app-workspace/index.html +12 -0
- package/templates/sy-lowcode-app-workspace/package.json +49 -0
- package/templates/sy-lowcode-app-workspace/postcss.config.cjs +6 -0
- package/templates/sy-lowcode-app-workspace/scripts/build-js-code.mjs +100 -0
- package/templates/sy-lowcode-app-workspace/src/dev/App.tsx +26 -0
- package/templates/sy-lowcode-app-workspace/src/forms/.gitkeep +1 -0
- package/templates/sy-lowcode-app-workspace/src/forms/README.md +48 -0
- package/templates/sy-lowcode-app-workspace/src/index.css +28 -0
- package/templates/sy-lowcode-app-workspace/src/js-code-nodes/.gitkeep +1 -0
- package/templates/sy-lowcode-app-workspace/src/js-code-nodes/types.d.ts +3 -0
- package/templates/sy-lowcode-app-workspace/src/main.tsx +36 -0
- package/templates/sy-lowcode-app-workspace/src/pages/.gitkeep +1 -0
- package/templates/sy-lowcode-app-workspace/src/shared/form-schema.ts +128 -0
- package/templates/sy-lowcode-app-workspace/src/types/app-workspace.types.ts +31 -0
- package/templates/sy-lowcode-app-workspace/tailwind.config.cjs +30 -0
- package/templates/sy-lowcode-app-workspace/tsconfig.app.json +24 -0
- package/templates/sy-lowcode-app-workspace/tsconfig.js-code-nodes.json +15 -0
- package/templates/sy-lowcode-app-workspace/tsconfig.json +7 -0
- package/templates/sy-lowcode-app-workspace/tsconfig.node.json +10 -0
- package/templates/sy-lowcode-app-workspace/vite.config.ts +32 -0
package/lib/cli.js
ADDED
|
@@ -0,0 +1,2423 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const { spawnSync } = require('child_process');
|
|
4
|
+
const {
|
|
5
|
+
CONFIG_FILE,
|
|
6
|
+
PROJECT_STATE_FILE,
|
|
7
|
+
getProfile,
|
|
8
|
+
loadConfig,
|
|
9
|
+
loadProjectState,
|
|
10
|
+
normalizeBaseUrl,
|
|
11
|
+
saveConfig,
|
|
12
|
+
saveProjectState,
|
|
13
|
+
} = require('./config');
|
|
14
|
+
const { requestJson } = require('./http');
|
|
15
|
+
const { getSkillStatusReport, installSkills } = require('./skills');
|
|
16
|
+
const { initWorkspace } = require('./workspace-init');
|
|
17
|
+
const {
|
|
18
|
+
fail,
|
|
19
|
+
openBrowser,
|
|
20
|
+
parseArgs,
|
|
21
|
+
print,
|
|
22
|
+
sleep,
|
|
23
|
+
warn,
|
|
24
|
+
writeJson,
|
|
25
|
+
} = require('./utils');
|
|
26
|
+
|
|
27
|
+
async function main(argv) {
|
|
28
|
+
const [command, ...rest] = argv;
|
|
29
|
+
if (!command || command === 'help' || command === '--help' || command === '-h') {
|
|
30
|
+
printHelp();
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (command === 'login') return login(rest);
|
|
35
|
+
if (command === 'env') return env(rest);
|
|
36
|
+
if (command === 'auth') return auth(rest);
|
|
37
|
+
if (command === 'platform') return platform(rest);
|
|
38
|
+
if (command === 'workspace') return workspace(rest);
|
|
39
|
+
if (command === 'app') return app(rest);
|
|
40
|
+
if (command === 'form') return form(rest);
|
|
41
|
+
if (command === 'page') return page(rest);
|
|
42
|
+
if (command === 'menu') return menu(rest);
|
|
43
|
+
if (command === 'workflow') return workflow(rest);
|
|
44
|
+
if (command === 'automation') return automation(rest);
|
|
45
|
+
if (command === 'permission') return permission(rest);
|
|
46
|
+
if (command === 'settings') return settings(rest);
|
|
47
|
+
if (command === 'inspect') return inspect(rest);
|
|
48
|
+
if (command === 'skill') return skill(rest);
|
|
49
|
+
if (command === 'commands') return commands(rest);
|
|
50
|
+
|
|
51
|
+
fail(`未知命令: ${command}`);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function printHelp() {
|
|
55
|
+
print(`OpenXiangda CLI
|
|
56
|
+
|
|
57
|
+
Usage:
|
|
58
|
+
openxiangda login <platform-url> [--profile name]
|
|
59
|
+
openxiangda platform add <name> <platform-url>
|
|
60
|
+
openxiangda platform list
|
|
61
|
+
openxiangda platform use <name>
|
|
62
|
+
openxiangda auth status|refresh|logout [--profile name]
|
|
63
|
+
openxiangda env [--profile name]
|
|
64
|
+
openxiangda workspace init [dir] [--name package-name] [--install] [--profile name --app-type APP_XXX]
|
|
65
|
+
openxiangda workspace bind --profile <name> --app-type <APP_XXX>
|
|
66
|
+
openxiangda workspace publish --profile <name>
|
|
67
|
+
openxiangda app list [--profile name] [--json]
|
|
68
|
+
openxiangda app create <name> [--profile name] [--description text]
|
|
69
|
+
openxiangda app snapshot <APP_XXX> [--profile name] [--json]
|
|
70
|
+
openxiangda form list [--profile name] [--json]
|
|
71
|
+
openxiangda form create <formCode> [--name text] [--type receipt] # low-level shell only
|
|
72
|
+
openxiangda form bind <formCode> --form-uuid <FORM_XXX>
|
|
73
|
+
openxiangda form pull <formCode|formUuid> [--json]
|
|
74
|
+
openxiangda form publish <formCode|formUuid> --bundle-url <url> # repair only
|
|
75
|
+
openxiangda page list [--profile name] [--json]
|
|
76
|
+
openxiangda page publish <pageCode> --entry-url <url> --version <v> --build-id <id> # repair only
|
|
77
|
+
openxiangda page bind <pageCode> --page-id <id>
|
|
78
|
+
openxiangda menu list [--profile name] [--json]
|
|
79
|
+
openxiangda menu create <menuCode> --name <text> --type <nav|receipt|display>
|
|
80
|
+
openxiangda menu bind <menuCode> --menu-id <id>
|
|
81
|
+
openxiangda workflow list [--profile name] [--form-code code] [--json]
|
|
82
|
+
openxiangda workflow create <workflowCode> --form-code <formCode> --definition-json <file>
|
|
83
|
+
openxiangda workflow bind <workflowCode> --workflow-id <id>
|
|
84
|
+
openxiangda workflow publish <workflowCode|workflowId>
|
|
85
|
+
openxiangda automation list [--profile name] [--json]
|
|
86
|
+
openxiangda automation create <automationCode> --name <text> --trigger-json <file> --definition-json <file>
|
|
87
|
+
openxiangda automation bind <automationCode> --automation-id <id>
|
|
88
|
+
openxiangda automation publish|enable|disable <automationCode|automationId>
|
|
89
|
+
openxiangda permission role-list|role-create|role-bind
|
|
90
|
+
openxiangda permission page-group-list|page-group-create|page-group-bind
|
|
91
|
+
openxiangda permission form-group-list|form-group-create|form-group-bind
|
|
92
|
+
openxiangda settings get|save|indexes|indexes-save|data-management|data-management-save|public-access
|
|
93
|
+
openxiangda inspect app|form|workflow|automation|permissions
|
|
94
|
+
openxiangda skill install [--agent codex] [--dest <skills-dir>] [--force] [--dry-run] [--json]
|
|
95
|
+
openxiangda skill status [--agent codex] [--dest <skills-dir>] [--json]
|
|
96
|
+
|
|
97
|
+
OpenXiangda 使用普通用户登录 token,不需要 AK/SK。
|
|
98
|
+
表单页、流程表单页和代码页的主链路是 sy-lowcode-app-workspace + openxiangda workspace publish。
|
|
99
|
+
JS_CODE V2 使用 trusted_node;AI 源码必须写在 src/js-code-nodes/<scriptCode>/index.ts,definition-json 中的 sourceFile.localPath 会在 validate/create 时先 TS 校验、再构建并上传为快照。`);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
async function platform(args) {
|
|
103
|
+
const [subcommand, ...rest] = args;
|
|
104
|
+
const { flags, positional } = parseArgs(rest);
|
|
105
|
+
const config = loadConfig();
|
|
106
|
+
|
|
107
|
+
if (subcommand === 'add') {
|
|
108
|
+
const [name, rawUrl] = positional;
|
|
109
|
+
if (!name || !rawUrl) {
|
|
110
|
+
fail('用法: openxiangda platform add <name> <platform-url>');
|
|
111
|
+
}
|
|
112
|
+
const baseUrl = normalizeBaseUrl(rawUrl);
|
|
113
|
+
const existing = config.profiles[name] || {};
|
|
114
|
+
const urlChanged = existing.baseUrl && existing.baseUrl !== baseUrl;
|
|
115
|
+
config.profiles[name] = {
|
|
116
|
+
name,
|
|
117
|
+
baseUrl,
|
|
118
|
+
user: urlChanged ? null : existing.user || null,
|
|
119
|
+
tenant: urlChanged ? null : existing.tenant || null,
|
|
120
|
+
token: urlChanged ? null : existing.token || null,
|
|
121
|
+
updatedAt: new Date().toISOString(),
|
|
122
|
+
};
|
|
123
|
+
config.currentProfile = config.currentProfile || name;
|
|
124
|
+
saveConfig(config);
|
|
125
|
+
print(`已添加平台 profile: ${name} -> ${baseUrl}`);
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (subcommand === 'list') {
|
|
130
|
+
const rows = Object.values(config.profiles).map(profile => ({
|
|
131
|
+
current: profile.name === config.currentProfile,
|
|
132
|
+
name: profile.name,
|
|
133
|
+
baseUrl: profile.baseUrl,
|
|
134
|
+
user: profile.user?.name || profile.user?.username || null,
|
|
135
|
+
tenant: profile.tenant?.name || profile.tenant?.code || null,
|
|
136
|
+
loggedIn: Boolean(profile.token?.accessToken),
|
|
137
|
+
}));
|
|
138
|
+
if (flags.json) return writeJson(rows);
|
|
139
|
+
if (rows.length === 0) {
|
|
140
|
+
print('暂无平台 profile。先执行 openxiangda platform add <name> <url>');
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
for (const row of rows) {
|
|
144
|
+
print(
|
|
145
|
+
`${row.current ? '*' : ' '} ${row.name} ${row.baseUrl} ${
|
|
146
|
+
row.loggedIn ? 'logged-in' : 'not-login'
|
|
147
|
+
}`
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (subcommand === 'use') {
|
|
154
|
+
const [name] = positional;
|
|
155
|
+
if (!name) fail('用法: openxiangda platform use <name>');
|
|
156
|
+
if (!config.profiles[name]) fail(`平台 profile 不存在: ${name}`);
|
|
157
|
+
config.currentProfile = name;
|
|
158
|
+
saveConfig(config);
|
|
159
|
+
print(`当前平台 profile: ${name}`);
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (subcommand === 'remove') {
|
|
164
|
+
const [name] = positional;
|
|
165
|
+
if (!name) fail('用法: openxiangda platform remove <name>');
|
|
166
|
+
delete config.profiles[name];
|
|
167
|
+
if (config.currentProfile === name) {
|
|
168
|
+
config.currentProfile = Object.keys(config.profiles)[0] || null;
|
|
169
|
+
}
|
|
170
|
+
saveConfig(config);
|
|
171
|
+
print(`已移除平台 profile: ${name}`);
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
fail('用法: openxiangda platform add|list|use|remove');
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
async function login(args) {
|
|
179
|
+
const { flags, positional } = parseArgs(args);
|
|
180
|
+
const config = loadConfig();
|
|
181
|
+
const rawUrl = positional[0];
|
|
182
|
+
const profileName = flags.profile || config.currentProfile || 'default';
|
|
183
|
+
let profile = config.profiles[profileName] || { name: profileName };
|
|
184
|
+
|
|
185
|
+
if (rawUrl) {
|
|
186
|
+
profile = {
|
|
187
|
+
...profile,
|
|
188
|
+
name: profileName,
|
|
189
|
+
baseUrl: normalizeBaseUrl(rawUrl),
|
|
190
|
+
};
|
|
191
|
+
config.profiles[profileName] = profile;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
if (!profile.baseUrl) {
|
|
195
|
+
fail('缺少平台地址。用法: openxiangda login <platform-url> [--profile name]');
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const sessionPayload = await requestJson(
|
|
199
|
+
profile.baseUrl,
|
|
200
|
+
'/openxiangda-api/v1/auth/cli-sessions',
|
|
201
|
+
{
|
|
202
|
+
method: 'POST',
|
|
203
|
+
body: { baseUrl: profile.baseUrl },
|
|
204
|
+
}
|
|
205
|
+
);
|
|
206
|
+
const session = normalizeLoginSession(unwrapApi(sessionPayload), profile.baseUrl);
|
|
207
|
+
|
|
208
|
+
print(`打开登录页并扫码确认授权: ${session.loginUrl}`);
|
|
209
|
+
renderTerminalQr(session.qrText || session.loginUrl);
|
|
210
|
+
if (!flags['no-open']) {
|
|
211
|
+
try {
|
|
212
|
+
openBrowser(session.loginUrl);
|
|
213
|
+
} catch (error) {
|
|
214
|
+
warn(`自动打开浏览器失败,请手动访问登录地址: ${error.message}`);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const deadline = Date.now() + Number(session.expireIn || 300) * 1000;
|
|
219
|
+
while (Date.now() < deadline) {
|
|
220
|
+
await sleep(2000);
|
|
221
|
+
const payload = await requestJson(
|
|
222
|
+
profile.baseUrl,
|
|
223
|
+
`/openxiangda-api/v1/auth/cli-sessions/${session.sessionId}`
|
|
224
|
+
);
|
|
225
|
+
const data = unwrapApi(payload);
|
|
226
|
+
if (data.status === 'authorized') {
|
|
227
|
+
profile = {
|
|
228
|
+
...profile,
|
|
229
|
+
token: {
|
|
230
|
+
accessToken: data.accessToken,
|
|
231
|
+
refreshToken: data.refreshToken,
|
|
232
|
+
accessTokenExpiresAt: data.accessTokenExpiresAt,
|
|
233
|
+
refreshTokenExpiresAt: data.refreshTokenExpiresAt,
|
|
234
|
+
},
|
|
235
|
+
user: data.user || null,
|
|
236
|
+
tenant: data.tenant || null,
|
|
237
|
+
updatedAt: new Date().toISOString(),
|
|
238
|
+
};
|
|
239
|
+
config.profiles[profileName] = profile;
|
|
240
|
+
config.currentProfile = profileName;
|
|
241
|
+
saveConfig(config);
|
|
242
|
+
|
|
243
|
+
const result = {
|
|
244
|
+
profile: profileName,
|
|
245
|
+
baseUrl: profile.baseUrl,
|
|
246
|
+
user: profile.user,
|
|
247
|
+
tenant: profile.tenant,
|
|
248
|
+
accessTokenExpiresAt: profile.token.accessTokenExpiresAt,
|
|
249
|
+
};
|
|
250
|
+
if (flags.json) writeJson(result);
|
|
251
|
+
else print(`登录成功: ${profileName} (${profile.baseUrl})`);
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
if (data.status === 'expired' || data.status === 'revoked') {
|
|
255
|
+
fail('CLI 登录会话已失效,请重新执行 openxiangda login');
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
fail('CLI 登录等待超时,请重新执行 openxiangda login');
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
function normalizeLoginSession(session, baseUrl) {
|
|
263
|
+
const loginUrl = normalizePlatformSessionUrl(session.loginUrl, baseUrl);
|
|
264
|
+
return {
|
|
265
|
+
...session,
|
|
266
|
+
loginUrl,
|
|
267
|
+
qrText: normalizePlatformSessionUrl(session.qrText, baseUrl) || loginUrl,
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
function normalizePlatformSessionUrl(value, baseUrl) {
|
|
272
|
+
if (!value) return value;
|
|
273
|
+
try {
|
|
274
|
+
const input = new URL(value);
|
|
275
|
+
const base = new URL(baseUrl);
|
|
276
|
+
const basePath = base.pathname.replace(/\/+$/, '');
|
|
277
|
+
if (
|
|
278
|
+
input.host === base.host &&
|
|
279
|
+
basePath &&
|
|
280
|
+
input.pathname.startsWith('/openxiangda-api/')
|
|
281
|
+
) {
|
|
282
|
+
return `${base.origin}${basePath}${input.pathname}${input.search}${input.hash}`;
|
|
283
|
+
}
|
|
284
|
+
return input.toString();
|
|
285
|
+
} catch {
|
|
286
|
+
return value;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
async function auth(args) {
|
|
291
|
+
const [subcommand, ...rest] = args;
|
|
292
|
+
const { flags } = parseArgs(rest);
|
|
293
|
+
|
|
294
|
+
if (subcommand === 'status') {
|
|
295
|
+
const config = loadConfig();
|
|
296
|
+
const { profileName, profile } = getProfile(config, flags.profile);
|
|
297
|
+
if (!profile.token?.accessToken) {
|
|
298
|
+
print(`未登录: ${profileName} (${profile.baseUrl})`);
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
const data = await requestWithAuth(config, profileName, '/openxiangda-api/v1/auth/whoami');
|
|
302
|
+
config.profiles[profileName].user = data.user;
|
|
303
|
+
config.profiles[profileName].tenant = data.tenant;
|
|
304
|
+
saveConfig(config);
|
|
305
|
+
if (flags.json) return writeJson(data);
|
|
306
|
+
print(
|
|
307
|
+
`已登录: ${profileName} ${profile.baseUrl} user=${data.user?.name || data.user?.id} tenant=${data.tenant?.name || data.tenant?.code}`
|
|
308
|
+
);
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
if (subcommand === 'refresh') {
|
|
313
|
+
const config = loadConfig();
|
|
314
|
+
const profileName = flags.profile || config.currentProfile;
|
|
315
|
+
if (!profileName) fail('未选择平台 profile');
|
|
316
|
+
const data = await refreshProfile(config, profileName);
|
|
317
|
+
saveConfig(config);
|
|
318
|
+
if (flags.json) return writeJson({ profile: profileName, ...data });
|
|
319
|
+
print(`token 已刷新: ${profileName}`);
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
if (subcommand === 'logout') {
|
|
324
|
+
const config = loadConfig();
|
|
325
|
+
const { profileName, profile } = getProfile(config, flags.profile);
|
|
326
|
+
if (profile.token?.accessToken) {
|
|
327
|
+
try {
|
|
328
|
+
await requestJson(profile.baseUrl, '/openxiangda-api/v1/auth/logout', {
|
|
329
|
+
method: 'POST',
|
|
330
|
+
accessToken: profile.token.accessToken,
|
|
331
|
+
});
|
|
332
|
+
} catch (error) {
|
|
333
|
+
warn(`服务端注销失败,本地 token 仍会清除: ${error.message}`);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
profile.token = null;
|
|
337
|
+
saveConfig(config);
|
|
338
|
+
print(`已退出登录: ${profileName}`);
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
fail('用法: openxiangda auth status|refresh|logout [--profile name]');
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
async function env(args) {
|
|
346
|
+
const { flags } = parseArgs(args);
|
|
347
|
+
const config = loadConfig();
|
|
348
|
+
const profileName = flags.profile || config.currentProfile;
|
|
349
|
+
const profile = profileName ? config.profiles[profileName] : null;
|
|
350
|
+
const state = loadProjectState();
|
|
351
|
+
const currentState = profileName ? state.profiles?.[profileName] : null;
|
|
352
|
+
const data = {
|
|
353
|
+
configFile: CONFIG_FILE,
|
|
354
|
+
projectStateFile: path.join(process.cwd(), PROJECT_STATE_FILE),
|
|
355
|
+
currentProfile: profileName || null,
|
|
356
|
+
baseUrl: profile?.baseUrl || null,
|
|
357
|
+
loggedIn: Boolean(profile?.token?.accessToken),
|
|
358
|
+
user: profile?.user || null,
|
|
359
|
+
tenant: profile?.tenant || null,
|
|
360
|
+
appType: currentState?.appType || null,
|
|
361
|
+
};
|
|
362
|
+
|
|
363
|
+
if (flags.json) return writeJson(data);
|
|
364
|
+
print(`配置文件: ${data.configFile}`);
|
|
365
|
+
print(`项目状态: ${data.projectStateFile}`);
|
|
366
|
+
print(`当前 profile: ${data.currentProfile || '-'}`);
|
|
367
|
+
print(`平台地址: ${data.baseUrl || '-'}`);
|
|
368
|
+
print(`登录状态: ${data.loggedIn ? '已登录' : '未登录'}`);
|
|
369
|
+
print(`项目绑定 appType: ${data.appType || '-'}`);
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
async function workspace(args) {
|
|
373
|
+
const [subcommand, ...rest] = args;
|
|
374
|
+
const { flags, positional } = parseArgs(rest);
|
|
375
|
+
const config = loadConfig();
|
|
376
|
+
|
|
377
|
+
if (subcommand === 'init') {
|
|
378
|
+
const result = initWorkspace({
|
|
379
|
+
dir: positional[0],
|
|
380
|
+
name: flags.name,
|
|
381
|
+
install: flags.install,
|
|
382
|
+
force: flags.force,
|
|
383
|
+
profile: flags.profile,
|
|
384
|
+
appType: flags['app-type'],
|
|
385
|
+
});
|
|
386
|
+
if (flags.json) return writeJson(result);
|
|
387
|
+
printWorkspaceInitReport(result);
|
|
388
|
+
return;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
const { profileName, profile } = getProfile(config, flags.profile);
|
|
392
|
+
|
|
393
|
+
if (subcommand === 'bind') {
|
|
394
|
+
const appType = flags['app-type'] || positional[0];
|
|
395
|
+
if (!appType) {
|
|
396
|
+
fail('用法: openxiangda workspace bind --profile <name> --app-type <APP_XXX>');
|
|
397
|
+
}
|
|
398
|
+
const state = loadProjectState();
|
|
399
|
+
state.profiles = state.profiles || {};
|
|
400
|
+
state.profiles[profileName] = {
|
|
401
|
+
...(state.profiles[profileName] || {}),
|
|
402
|
+
baseUrl: profile.baseUrl,
|
|
403
|
+
appType,
|
|
404
|
+
resources: state.profiles[profileName]?.resources || {
|
|
405
|
+
forms: {},
|
|
406
|
+
pages: {},
|
|
407
|
+
workflows: {},
|
|
408
|
+
automations: {},
|
|
409
|
+
menus: {},
|
|
410
|
+
},
|
|
411
|
+
updatedAt: new Date().toISOString(),
|
|
412
|
+
};
|
|
413
|
+
saveProjectState(state);
|
|
414
|
+
print(`当前工作区已绑定 ${profileName}: ${appType}`);
|
|
415
|
+
return;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
if (subcommand === 'publish') {
|
|
419
|
+
const state = loadProjectState();
|
|
420
|
+
const bound = state.profiles?.[profileName];
|
|
421
|
+
if (!bound?.appType) {
|
|
422
|
+
fail(
|
|
423
|
+
`profile ${profileName} 未绑定应用。先执行 openxiangda workspace bind --profile ${profileName} --app-type APP_XXX`
|
|
424
|
+
);
|
|
425
|
+
}
|
|
426
|
+
await requestWithAuth(
|
|
427
|
+
config,
|
|
428
|
+
profileName,
|
|
429
|
+
`/openxiangda-api/v1/apps/${encodeURIComponent(bound.appType)}/snapshot`
|
|
430
|
+
);
|
|
431
|
+
runWorkspacePublish(profileName, profile, bound.appType);
|
|
432
|
+
return;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
fail('用法: openxiangda workspace init|bind|publish');
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
async function app(args) {
|
|
439
|
+
const [subcommand, ...rest] = args;
|
|
440
|
+
const { flags, positional } = parseArgs(rest);
|
|
441
|
+
const config = loadConfig();
|
|
442
|
+
const profileName = flags.profile || config.currentProfile;
|
|
443
|
+
|
|
444
|
+
if (subcommand === 'list') {
|
|
445
|
+
const data = await requestWithAuth(config, profileName, '/openxiangda-api/v1/apps/');
|
|
446
|
+
if (flags.json) return writeJson(data);
|
|
447
|
+
print(JSON.stringify(data, null, 2));
|
|
448
|
+
return;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
if (subcommand === 'create') {
|
|
452
|
+
const name = flags.name || positional.join(' ');
|
|
453
|
+
if (!name) fail('用法: openxiangda app create <name> [--profile name]');
|
|
454
|
+
const data = await requestWithAuth(config, profileName, '/openxiangda-api/v1/apps/', {
|
|
455
|
+
method: 'POST',
|
|
456
|
+
body: {
|
|
457
|
+
name,
|
|
458
|
+
description: flags.description || '',
|
|
459
|
+
iconfontCss: flags['iconfont-css'] || '',
|
|
460
|
+
},
|
|
461
|
+
});
|
|
462
|
+
if (flags.json) return writeJson(data);
|
|
463
|
+
print(JSON.stringify(data, null, 2));
|
|
464
|
+
return;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
if (subcommand !== 'snapshot') {
|
|
468
|
+
fail('用法: openxiangda app list|create|snapshot');
|
|
469
|
+
}
|
|
470
|
+
const [appType] = positional;
|
|
471
|
+
if (!appType) fail('缺少 appType');
|
|
472
|
+
const data = await requestWithAuth(
|
|
473
|
+
config,
|
|
474
|
+
profileName,
|
|
475
|
+
`/openxiangda-api/v1/apps/${encodeURIComponent(appType)}/snapshot`
|
|
476
|
+
);
|
|
477
|
+
if (flags.json) return writeJson(data);
|
|
478
|
+
print(JSON.stringify(data, null, 2));
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
async function form(args) {
|
|
482
|
+
const [subcommand, ...rest] = args;
|
|
483
|
+
const { flags, positional } = parseArgs(rest);
|
|
484
|
+
const config = loadConfig();
|
|
485
|
+
const profileName = flags.profile || config.currentProfile;
|
|
486
|
+
|
|
487
|
+
if (subcommand === 'list') {
|
|
488
|
+
const target = getWorkspaceTarget(config, profileName, flags);
|
|
489
|
+
const data = await requestWithAuth(
|
|
490
|
+
config,
|
|
491
|
+
target.profileName,
|
|
492
|
+
`/openxiangda-api/v1/apps/${encodeURIComponent(target.appType)}/forms`
|
|
493
|
+
);
|
|
494
|
+
if (flags.json) return writeJson(data);
|
|
495
|
+
print(JSON.stringify(data, null, 2));
|
|
496
|
+
return;
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
if (subcommand === 'bind') {
|
|
500
|
+
const [formCode, maybeFormUuid] = positional;
|
|
501
|
+
const formUuid = flags['form-uuid'] || maybeFormUuid;
|
|
502
|
+
if (!formCode || !formUuid) {
|
|
503
|
+
fail('用法: openxiangda form bind <formCode> --form-uuid <FORM_XXX>');
|
|
504
|
+
}
|
|
505
|
+
const target = getWorkspaceTarget(config, profileName, flags);
|
|
506
|
+
saveFormResource(target, formCode, formUuid);
|
|
507
|
+
print(`已绑定表单 ${formCode}: ${formUuid}`);
|
|
508
|
+
return;
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
if (subcommand === 'create') {
|
|
512
|
+
const [formCode] = positional;
|
|
513
|
+
const name = flags.name || positional.slice(1).join(' ') || formCode;
|
|
514
|
+
if (!formCode || !name) {
|
|
515
|
+
fail('用法: openxiangda form create <formCode> [--name text]');
|
|
516
|
+
}
|
|
517
|
+
const target = getWorkspaceTarget(config, profileName, flags);
|
|
518
|
+
const data = await requestWithAuth(
|
|
519
|
+
config,
|
|
520
|
+
target.profileName,
|
|
521
|
+
`/openxiangda-api/v1/apps/${encodeURIComponent(target.appType)}/forms`,
|
|
522
|
+
{
|
|
523
|
+
method: 'POST',
|
|
524
|
+
body: {
|
|
525
|
+
name,
|
|
526
|
+
formType: flags.type || 'receipt',
|
|
527
|
+
formUuid: flags['form-uuid'],
|
|
528
|
+
builderVersion: flags['builder-version'],
|
|
529
|
+
createMenu: Boolean(flags.menu),
|
|
530
|
+
menuParentId: flags['menu-parent-id'],
|
|
531
|
+
menuIcon: flags['menu-icon'],
|
|
532
|
+
},
|
|
533
|
+
}
|
|
534
|
+
);
|
|
535
|
+
const formUuid = data?.form?.formUuid || data?.formUuid;
|
|
536
|
+
if (formUuid) {
|
|
537
|
+
saveFormResource(target, formCode, formUuid, { name });
|
|
538
|
+
}
|
|
539
|
+
if (flags.json) return writeJson(data);
|
|
540
|
+
print(JSON.stringify(data, null, 2));
|
|
541
|
+
return;
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
if (subcommand === 'pull') {
|
|
545
|
+
const [formKey] = positional;
|
|
546
|
+
if (!formKey) fail('用法: openxiangda form pull <formCode|formUuid>');
|
|
547
|
+
const target = getWorkspaceTarget(config, profileName, flags);
|
|
548
|
+
const formUuid = resolveFormUuid(target.bound, formKey, flags);
|
|
549
|
+
const data = await requestWithAuth(
|
|
550
|
+
config,
|
|
551
|
+
target.profileName,
|
|
552
|
+
`/openxiangda-api/v1/apps/${encodeURIComponent(target.appType)}/forms/${encodeURIComponent(formUuid)}`
|
|
553
|
+
);
|
|
554
|
+
if (flags.json) return writeJson(data);
|
|
555
|
+
print(JSON.stringify(data, null, 2));
|
|
556
|
+
return;
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
if (subcommand === 'publish') {
|
|
560
|
+
const [formKey] = positional;
|
|
561
|
+
if (!formKey || !flags['bundle-url']) {
|
|
562
|
+
fail(
|
|
563
|
+
'用法: openxiangda form publish <formCode|formUuid> --bundle-url <url>'
|
|
564
|
+
);
|
|
565
|
+
}
|
|
566
|
+
const target = getWorkspaceTarget(config, profileName, flags);
|
|
567
|
+
const formUuid = resolveFormUuid(target.bound, formKey, flags);
|
|
568
|
+
const data = await requestWithAuth(
|
|
569
|
+
config,
|
|
570
|
+
target.profileName,
|
|
571
|
+
`/openxiangda-api/v1/apps/${encodeURIComponent(target.appType)}/forms/${encodeURIComponent(formUuid)}/publish`,
|
|
572
|
+
{
|
|
573
|
+
method: 'POST',
|
|
574
|
+
body: {
|
|
575
|
+
bundleUrl: flags['bundle-url'],
|
|
576
|
+
cssUrl: flags['css-url'] || null,
|
|
577
|
+
version: flags.version || '',
|
|
578
|
+
},
|
|
579
|
+
}
|
|
580
|
+
);
|
|
581
|
+
if (flags.json) return writeJson(data);
|
|
582
|
+
print(JSON.stringify(data, null, 2));
|
|
583
|
+
return;
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
fail('用法: openxiangda form list|create|bind|pull|publish');
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
async function page(args) {
|
|
590
|
+
const [subcommand, ...rest] = args;
|
|
591
|
+
const { flags, positional } = parseArgs(rest);
|
|
592
|
+
const config = loadConfig();
|
|
593
|
+
const profileName = flags.profile || config.currentProfile;
|
|
594
|
+
|
|
595
|
+
if (subcommand === 'list') {
|
|
596
|
+
const target = getWorkspaceTarget(config, profileName, flags);
|
|
597
|
+
const data = await requestWithAuth(
|
|
598
|
+
config,
|
|
599
|
+
target.profileName,
|
|
600
|
+
`/openxiangda-api/v1/apps/${encodeURIComponent(target.appType)}/pages`
|
|
601
|
+
);
|
|
602
|
+
if (flags.json) return writeJson(data);
|
|
603
|
+
print(JSON.stringify(data, null, 2));
|
|
604
|
+
return;
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
if (subcommand === 'bind') {
|
|
608
|
+
const [pageCode, maybePageId] = positional;
|
|
609
|
+
const pageId = flags['page-id'] || maybePageId;
|
|
610
|
+
if (!pageCode || !pageId) {
|
|
611
|
+
fail('用法: openxiangda page bind <pageCode> --page-id <id>');
|
|
612
|
+
}
|
|
613
|
+
const target = getWorkspaceTarget(config, profileName, flags);
|
|
614
|
+
savePageResource(target, pageCode, pageId);
|
|
615
|
+
print(`已绑定代码页 ${pageCode}: ${pageId}`);
|
|
616
|
+
return;
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
if (subcommand === 'releases') {
|
|
620
|
+
const target = getWorkspaceTarget(config, profileName, flags);
|
|
621
|
+
const data = await requestWithAuth(
|
|
622
|
+
config,
|
|
623
|
+
target.profileName,
|
|
624
|
+
`/openxiangda-api/v1/apps/${encodeURIComponent(target.appType)}/pages/releases`
|
|
625
|
+
);
|
|
626
|
+
if (flags.json) return writeJson(data);
|
|
627
|
+
print(JSON.stringify(data, null, 2));
|
|
628
|
+
return;
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
if (subcommand === 'activate') {
|
|
632
|
+
const target = getWorkspaceTarget(config, profileName, flags);
|
|
633
|
+
if (!flags.version || !flags['build-id']) {
|
|
634
|
+
fail('用法: openxiangda page activate --version <v> --build-id <id>');
|
|
635
|
+
}
|
|
636
|
+
const data = await requestWithAuth(
|
|
637
|
+
config,
|
|
638
|
+
target.profileName,
|
|
639
|
+
`/openxiangda-api/v1/apps/${encodeURIComponent(target.appType)}/pages/releases/activate`,
|
|
640
|
+
{
|
|
641
|
+
method: 'POST',
|
|
642
|
+
body: {
|
|
643
|
+
version: flags.version,
|
|
644
|
+
buildId: flags['build-id'],
|
|
645
|
+
},
|
|
646
|
+
}
|
|
647
|
+
);
|
|
648
|
+
if (flags.json) return writeJson(data);
|
|
649
|
+
print(JSON.stringify(data, null, 2));
|
|
650
|
+
return;
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
if (subcommand === 'publish') {
|
|
654
|
+
const [pageCode] = positional;
|
|
655
|
+
if (!pageCode || !flags['entry-url'] || !flags.version || !flags['build-id']) {
|
|
656
|
+
fail(
|
|
657
|
+
'用法: openxiangda page publish <pageCode> --entry-url <url> --version <v> --build-id <id>'
|
|
658
|
+
);
|
|
659
|
+
}
|
|
660
|
+
const target = getWorkspaceTarget(config, profileName, flags);
|
|
661
|
+
const data = await requestWithAuth(
|
|
662
|
+
config,
|
|
663
|
+
target.profileName,
|
|
664
|
+
`/openxiangda-api/v1/apps/${encodeURIComponent(target.appType)}/pages/${encodeURIComponent(pageCode)}/publish`,
|
|
665
|
+
{
|
|
666
|
+
method: 'POST',
|
|
667
|
+
body: {
|
|
668
|
+
name: flags.name || pageCode,
|
|
669
|
+
description: flags.description || '',
|
|
670
|
+
version: flags.version,
|
|
671
|
+
buildId: flags['build-id'],
|
|
672
|
+
route: flags.route ? JSON.parse(flags.route) : {},
|
|
673
|
+
props: flags.props ? JSON.parse(flags.props) : {},
|
|
674
|
+
runtime: {
|
|
675
|
+
entryUrl: flags['entry-url'],
|
|
676
|
+
cssUrls: splitList(flags['css-urls']),
|
|
677
|
+
jsUrls: splitList(flags['js-urls']),
|
|
678
|
+
framework: flags.framework || 'react',
|
|
679
|
+
frameworkVersion: flags['framework-version'] || '',
|
|
680
|
+
cssIsolation: flags['css-isolation'] || 'namespace',
|
|
681
|
+
format: flags.format || 'esm',
|
|
682
|
+
},
|
|
683
|
+
menu: flags.menu
|
|
684
|
+
? {
|
|
685
|
+
enabled: true,
|
|
686
|
+
name: flags['menu-name'] || flags.name || pageCode,
|
|
687
|
+
parentId: flags['menu-parent-id'] || null,
|
|
688
|
+
icon: flags['menu-icon'] || null,
|
|
689
|
+
}
|
|
690
|
+
: { enabled: false },
|
|
691
|
+
},
|
|
692
|
+
}
|
|
693
|
+
);
|
|
694
|
+
const item = Array.isArray(data?.items) ? data.items[0] : null;
|
|
695
|
+
if (item?.pageId) {
|
|
696
|
+
savePageResource(target, pageCode, item.pageId, {
|
|
697
|
+
routeKey: item.routeKey,
|
|
698
|
+
legacyFormUuid: item.legacyFormUuid,
|
|
699
|
+
});
|
|
700
|
+
}
|
|
701
|
+
if (flags.json) return writeJson(data);
|
|
702
|
+
print(JSON.stringify(data, null, 2));
|
|
703
|
+
return;
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
fail('用法: openxiangda page list|publish|bind|releases|activate');
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
async function menu(args) {
|
|
710
|
+
const [subcommand, ...rest] = args;
|
|
711
|
+
const { flags, positional } = parseArgs(rest);
|
|
712
|
+
const config = loadConfig();
|
|
713
|
+
const profileName = flags.profile || config.currentProfile;
|
|
714
|
+
|
|
715
|
+
if (subcommand === 'list') {
|
|
716
|
+
const target = getWorkspaceTarget(config, profileName, flags);
|
|
717
|
+
const data = await requestWithAuth(
|
|
718
|
+
config,
|
|
719
|
+
target.profileName,
|
|
720
|
+
`/openxiangda-api/v1/apps/${encodeURIComponent(target.appType)}/menus`
|
|
721
|
+
);
|
|
722
|
+
if (flags.json) return writeJson(data);
|
|
723
|
+
print(JSON.stringify(data, null, 2));
|
|
724
|
+
return;
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
if (subcommand === 'bind') {
|
|
728
|
+
const [menuCode, maybeMenuId] = positional;
|
|
729
|
+
const menuId = flags['menu-id'] || maybeMenuId;
|
|
730
|
+
if (!menuCode || !menuId) {
|
|
731
|
+
fail('用法: openxiangda menu bind <menuCode> --menu-id <id>');
|
|
732
|
+
}
|
|
733
|
+
const target = getWorkspaceTarget(config, profileName, flags);
|
|
734
|
+
saveMenuResource(target, menuCode, menuId);
|
|
735
|
+
print(`已绑定菜单 ${menuCode}: ${menuId}`);
|
|
736
|
+
return;
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
if (subcommand === 'create') {
|
|
740
|
+
const [menuCode] = positional;
|
|
741
|
+
const name = flags.name || positional.slice(1).join(' ') || menuCode;
|
|
742
|
+
if (!menuCode || !name) {
|
|
743
|
+
fail('用法: openxiangda menu create <menuCode> --name <text>');
|
|
744
|
+
}
|
|
745
|
+
const target = getWorkspaceTarget(config, profileName, flags);
|
|
746
|
+
const data = await requestWithAuth(
|
|
747
|
+
config,
|
|
748
|
+
target.profileName,
|
|
749
|
+
`/openxiangda-api/v1/apps/${encodeURIComponent(target.appType)}/menus`,
|
|
750
|
+
{
|
|
751
|
+
method: 'POST',
|
|
752
|
+
body: {
|
|
753
|
+
name,
|
|
754
|
+
type: flags.type || 'nav',
|
|
755
|
+
formUuid: flags['form-uuid'] || resolveOptionalFormUuid(target.bound, flags['form-code']),
|
|
756
|
+
pageId: flags['page-id'] || resolveOptionalPageId(target.bound, flags['page-code']),
|
|
757
|
+
parentId: flags['parent-id'] || null,
|
|
758
|
+
icon: flags.icon || null,
|
|
759
|
+
},
|
|
760
|
+
}
|
|
761
|
+
);
|
|
762
|
+
if (data?.id) saveMenuResource(target, menuCode, data.id, { name });
|
|
763
|
+
if (flags.json) return writeJson(data);
|
|
764
|
+
print(JSON.stringify(data, null, 2));
|
|
765
|
+
return;
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
if (subcommand === 'delete') {
|
|
769
|
+
const [menuKey] = positional;
|
|
770
|
+
if (!menuKey) fail('用法: openxiangda menu delete <menuCode|menuId>');
|
|
771
|
+
const target = getWorkspaceTarget(config, profileName, flags);
|
|
772
|
+
const menuId = resolveMenuId(target.bound, menuKey, flags);
|
|
773
|
+
const data = await requestWithAuth(
|
|
774
|
+
config,
|
|
775
|
+
target.profileName,
|
|
776
|
+
`/openxiangda-api/v1/apps/${encodeURIComponent(target.appType)}/menus/${encodeURIComponent(menuId)}`,
|
|
777
|
+
{ method: 'DELETE' }
|
|
778
|
+
);
|
|
779
|
+
if (target.bound.resources?.menus?.[menuKey]) {
|
|
780
|
+
delete target.bound.resources.menus[menuKey];
|
|
781
|
+
saveProjectState(target.state);
|
|
782
|
+
}
|
|
783
|
+
if (flags.json) return writeJson(data);
|
|
784
|
+
print(JSON.stringify(data, null, 2));
|
|
785
|
+
return;
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
fail('用法: openxiangda menu list|create|bind|delete');
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
async function workflow(args) {
|
|
792
|
+
const [subcommand, ...rest] = args;
|
|
793
|
+
const { flags, positional } = parseArgs(rest);
|
|
794
|
+
const config = loadConfig();
|
|
795
|
+
const profileName = flags.profile || config.currentProfile;
|
|
796
|
+
|
|
797
|
+
if (subcommand === 'list') {
|
|
798
|
+
const target = getWorkspaceTarget(config, profileName, flags);
|
|
799
|
+
const formUuid = flags['form-uuid'] || resolveOptionalFormUuid(target.bound, flags['form-code']);
|
|
800
|
+
const data = await requestWithAuth(
|
|
801
|
+
config,
|
|
802
|
+
target.profileName,
|
|
803
|
+
apiPathWithQuery(
|
|
804
|
+
`/openxiangda-api/v1/apps/${encodeURIComponent(target.appType)}/workflows`,
|
|
805
|
+
{
|
|
806
|
+
formUuid,
|
|
807
|
+
isPublished: flags.published,
|
|
808
|
+
page: flags.page,
|
|
809
|
+
pageSize: flags['page-size'],
|
|
810
|
+
}
|
|
811
|
+
)
|
|
812
|
+
);
|
|
813
|
+
if (flags.json) return writeJson(data);
|
|
814
|
+
print(JSON.stringify(data, null, 2));
|
|
815
|
+
return;
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
if (subcommand === 'bind') {
|
|
819
|
+
const [workflowCode, maybeWorkflowId] = positional;
|
|
820
|
+
const workflowId = flags['workflow-id'] || maybeWorkflowId;
|
|
821
|
+
if (!workflowCode || !workflowId) {
|
|
822
|
+
fail('用法: openxiangda workflow bind <workflowCode> --workflow-id <id>');
|
|
823
|
+
}
|
|
824
|
+
const target = getWorkspaceTarget(config, profileName, flags);
|
|
825
|
+
saveWorkflowResource(target, workflowCode, workflowId, {
|
|
826
|
+
formUuid: flags['form-uuid'] || resolveOptionalFormUuid(target.bound, flags['form-code']),
|
|
827
|
+
});
|
|
828
|
+
print(`已绑定流程 ${workflowCode}: ${workflowId}`);
|
|
829
|
+
return;
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
if (subcommand === 'create') {
|
|
833
|
+
const [workflowCode] = positional;
|
|
834
|
+
const target = getWorkspaceTarget(config, profileName, flags);
|
|
835
|
+
const formUuid = flags['form-uuid'] || resolveOptionalFormUuid(target.bound, flags['form-code']);
|
|
836
|
+
if (!workflowCode || !formUuid || !flags['definition-json']) {
|
|
837
|
+
fail(
|
|
838
|
+
'用法: openxiangda workflow create <workflowCode> --form-code <formCode>|--form-uuid <FORM_XXX> --definition-json <file>'
|
|
839
|
+
);
|
|
840
|
+
}
|
|
841
|
+
const definitionJson = await readDefinitionJsonArg(
|
|
842
|
+
config,
|
|
843
|
+
target.profileName,
|
|
844
|
+
flags['definition-json'],
|
|
845
|
+
'definition-json'
|
|
846
|
+
);
|
|
847
|
+
const data = await requestWithAuth(
|
|
848
|
+
config,
|
|
849
|
+
target.profileName,
|
|
850
|
+
`/openxiangda-api/v1/apps/${encodeURIComponent(target.appType)}/workflows`,
|
|
851
|
+
{
|
|
852
|
+
method: 'POST',
|
|
853
|
+
body: {
|
|
854
|
+
formUuid,
|
|
855
|
+
definitionJson,
|
|
856
|
+
...(flags['view-json']
|
|
857
|
+
? { viewJson: readJsonArg(flags['view-json'], 'view-json') }
|
|
858
|
+
: {}),
|
|
859
|
+
},
|
|
860
|
+
}
|
|
861
|
+
);
|
|
862
|
+
if (data?.id) saveWorkflowResource(target, workflowCode, data.id, { formUuid });
|
|
863
|
+
if (flags.publish && data?.id) {
|
|
864
|
+
const published = await requestWithAuth(
|
|
865
|
+
config,
|
|
866
|
+
target.profileName,
|
|
867
|
+
`/openxiangda-api/v1/apps/${encodeURIComponent(target.appType)}/workflows/${encodeURIComponent(data.id)}/publish`,
|
|
868
|
+
{ method: 'POST', body: { isPublished: true } }
|
|
869
|
+
);
|
|
870
|
+
if (flags.json) return writeJson(published);
|
|
871
|
+
print(JSON.stringify(published, null, 2));
|
|
872
|
+
return;
|
|
873
|
+
}
|
|
874
|
+
if (flags.json) return writeJson(data);
|
|
875
|
+
print(JSON.stringify(data, null, 2));
|
|
876
|
+
return;
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
if (subcommand === 'pull') {
|
|
880
|
+
const [workflowKey] = positional;
|
|
881
|
+
if (!workflowKey) fail('用法: openxiangda workflow pull <workflowCode|workflowId>');
|
|
882
|
+
const target = getWorkspaceTarget(config, profileName, flags);
|
|
883
|
+
const workflowId = resolveWorkflowId(target.bound, workflowKey, flags);
|
|
884
|
+
const data = await requestWithAuth(
|
|
885
|
+
config,
|
|
886
|
+
target.profileName,
|
|
887
|
+
`/openxiangda-api/v1/apps/${encodeURIComponent(target.appType)}/workflows/${encodeURIComponent(workflowId)}`
|
|
888
|
+
);
|
|
889
|
+
if (flags.json) return writeJson(data);
|
|
890
|
+
print(JSON.stringify(data, null, 2));
|
|
891
|
+
return;
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
if (subcommand === 'publish') {
|
|
895
|
+
const [workflowKey] = positional;
|
|
896
|
+
if (!workflowKey) fail('用法: openxiangda workflow publish <workflowCode|workflowId>');
|
|
897
|
+
const target = getWorkspaceTarget(config, profileName, flags);
|
|
898
|
+
const workflowId = resolveWorkflowId(target.bound, workflowKey, flags);
|
|
899
|
+
const data = await requestWithAuth(
|
|
900
|
+
config,
|
|
901
|
+
target.profileName,
|
|
902
|
+
`/openxiangda-api/v1/apps/${encodeURIComponent(target.appType)}/workflows/${encodeURIComponent(workflowId)}/publish`,
|
|
903
|
+
{
|
|
904
|
+
method: 'POST',
|
|
905
|
+
body: { isPublished: !flags.unpublish },
|
|
906
|
+
}
|
|
907
|
+
);
|
|
908
|
+
if (flags.json) return writeJson(data);
|
|
909
|
+
print(JSON.stringify(data, null, 2));
|
|
910
|
+
return;
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
if (subcommand === 'delete') {
|
|
914
|
+
const [workflowKey] = positional;
|
|
915
|
+
if (!workflowKey) fail('用法: openxiangda workflow delete <workflowCode|workflowId>');
|
|
916
|
+
const target = getWorkspaceTarget(config, profileName, flags);
|
|
917
|
+
const workflowId = resolveWorkflowId(target.bound, workflowKey, flags);
|
|
918
|
+
const data = await requestWithAuth(
|
|
919
|
+
config,
|
|
920
|
+
target.profileName,
|
|
921
|
+
`/openxiangda-api/v1/apps/${encodeURIComponent(target.appType)}/workflows/${encodeURIComponent(workflowId)}`,
|
|
922
|
+
{ method: 'DELETE' }
|
|
923
|
+
);
|
|
924
|
+
if (target.bound.resources?.workflows?.[workflowKey]) {
|
|
925
|
+
delete target.bound.resources.workflows[workflowKey];
|
|
926
|
+
saveProjectState(target.state);
|
|
927
|
+
}
|
|
928
|
+
if (flags.json) return writeJson(data);
|
|
929
|
+
print(JSON.stringify(data, null, 2));
|
|
930
|
+
return;
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
if (subcommand === 'validate') {
|
|
934
|
+
const target = getWorkspaceTarget(config, profileName, flags);
|
|
935
|
+
if (!flags['definition-json']) {
|
|
936
|
+
fail('用法: openxiangda workflow validate --definition-json <file>');
|
|
937
|
+
}
|
|
938
|
+
const definitionJson = await readDefinitionJsonArg(
|
|
939
|
+
config,
|
|
940
|
+
target.profileName,
|
|
941
|
+
flags['definition-json'],
|
|
942
|
+
'definition-json'
|
|
943
|
+
);
|
|
944
|
+
const data = await requestWithAuth(
|
|
945
|
+
config,
|
|
946
|
+
target.profileName,
|
|
947
|
+
`/openxiangda-api/v1/apps/${encodeURIComponent(target.appType)}/workflows/definition/validate`,
|
|
948
|
+
{
|
|
949
|
+
method: 'POST',
|
|
950
|
+
body: {
|
|
951
|
+
definitionJson,
|
|
952
|
+
requirePublishNodes: Boolean(flags.publish),
|
|
953
|
+
},
|
|
954
|
+
}
|
|
955
|
+
);
|
|
956
|
+
if (flags.json) return writeJson(data);
|
|
957
|
+
print(JSON.stringify(data, null, 2));
|
|
958
|
+
return;
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
fail('用法: openxiangda workflow list|create|bind|pull|publish|delete|validate');
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
async function automation(args) {
|
|
965
|
+
const [subcommand, ...rest] = args;
|
|
966
|
+
const { flags, positional } = parseArgs(rest);
|
|
967
|
+
const config = loadConfig();
|
|
968
|
+
const profileName = flags.profile || config.currentProfile;
|
|
969
|
+
|
|
970
|
+
if (subcommand === 'list') {
|
|
971
|
+
const target = getWorkspaceTarget(config, profileName, flags);
|
|
972
|
+
const formUuid = flags['form-uuid'] || resolveOptionalFormUuid(target.bound, flags['form-code']);
|
|
973
|
+
const data = await requestWithAuth(
|
|
974
|
+
config,
|
|
975
|
+
target.profileName,
|
|
976
|
+
apiPathWithQuery(
|
|
977
|
+
`/openxiangda-api/v1/apps/${encodeURIComponent(target.appType)}/automations`,
|
|
978
|
+
{
|
|
979
|
+
formUuid,
|
|
980
|
+
isPublished: flags.published,
|
|
981
|
+
isEnabled: flags.enabled,
|
|
982
|
+
triggerType: flags['trigger-type'],
|
|
983
|
+
keyword: flags.keyword,
|
|
984
|
+
page: flags.page,
|
|
985
|
+
pageSize: flags['page-size'],
|
|
986
|
+
}
|
|
987
|
+
)
|
|
988
|
+
);
|
|
989
|
+
if (flags.json) return writeJson(data);
|
|
990
|
+
print(JSON.stringify(data, null, 2));
|
|
991
|
+
return;
|
|
992
|
+
}
|
|
993
|
+
|
|
994
|
+
if (subcommand === 'bind') {
|
|
995
|
+
const [automationCode, maybeAutomationId] = positional;
|
|
996
|
+
const automationId = flags['automation-id'] || maybeAutomationId;
|
|
997
|
+
if (!automationCode || !automationId) {
|
|
998
|
+
fail('用法: openxiangda automation bind <automationCode> --automation-id <id>');
|
|
999
|
+
}
|
|
1000
|
+
const target = getWorkspaceTarget(config, profileName, flags);
|
|
1001
|
+
saveAutomationResource(target, automationCode, automationId, {
|
|
1002
|
+
formUuid: flags['form-uuid'] || resolveOptionalFormUuid(target.bound, flags['form-code']),
|
|
1003
|
+
});
|
|
1004
|
+
print(`已绑定自动化 ${automationCode}: ${automationId}`);
|
|
1005
|
+
return;
|
|
1006
|
+
}
|
|
1007
|
+
|
|
1008
|
+
if (subcommand === 'create') {
|
|
1009
|
+
const [automationCode] = positional;
|
|
1010
|
+
const target = getWorkspaceTarget(config, profileName, flags);
|
|
1011
|
+
if (!automationCode || !flags.name || !flags['trigger-json'] || !flags['definition-json']) {
|
|
1012
|
+
fail(
|
|
1013
|
+
'用法: openxiangda automation create <automationCode> --name <text> --trigger-json <file> --definition-json <file>'
|
|
1014
|
+
);
|
|
1015
|
+
}
|
|
1016
|
+
const formUuid = flags['form-uuid'] || resolveOptionalFormUuid(target.bound, flags['form-code']);
|
|
1017
|
+
const triggerConfig = readJsonArg(flags['trigger-json'], 'trigger-json');
|
|
1018
|
+
if (formUuid && !triggerConfig.formUuid) triggerConfig.formUuid = formUuid;
|
|
1019
|
+
if (!triggerConfig.appType) triggerConfig.appType = target.appType;
|
|
1020
|
+
|
|
1021
|
+
const definitionJson = await readDefinitionJsonArg(
|
|
1022
|
+
config,
|
|
1023
|
+
target.profileName,
|
|
1024
|
+
flags['definition-json'],
|
|
1025
|
+
'definition-json'
|
|
1026
|
+
);
|
|
1027
|
+
const data = await requestWithAuth(
|
|
1028
|
+
config,
|
|
1029
|
+
target.profileName,
|
|
1030
|
+
`/openxiangda-api/v1/apps/${encodeURIComponent(target.appType)}/automations`,
|
|
1031
|
+
{
|
|
1032
|
+
method: 'POST',
|
|
1033
|
+
body: {
|
|
1034
|
+
name: flags.name,
|
|
1035
|
+
description: flags.description || '',
|
|
1036
|
+
formUuid,
|
|
1037
|
+
triggerConfig,
|
|
1038
|
+
definitionJson,
|
|
1039
|
+
...(flags['view-json']
|
|
1040
|
+
? { viewJson: readJsonArg(flags['view-json'], 'view-json') }
|
|
1041
|
+
: {}),
|
|
1042
|
+
tags: flags.tags || '',
|
|
1043
|
+
},
|
|
1044
|
+
}
|
|
1045
|
+
);
|
|
1046
|
+
if (data?.id) saveAutomationResource(target, automationCode, data.id, { formUuid });
|
|
1047
|
+
if (flags.publish && data?.id) {
|
|
1048
|
+
const published = await requestWithAuth(
|
|
1049
|
+
config,
|
|
1050
|
+
target.profileName,
|
|
1051
|
+
`/openxiangda-api/v1/apps/${encodeURIComponent(target.appType)}/automations/${encodeURIComponent(data.id)}/publish`,
|
|
1052
|
+
{ method: 'POST' }
|
|
1053
|
+
);
|
|
1054
|
+
if (flags.json) return writeJson(published);
|
|
1055
|
+
print(JSON.stringify(published, null, 2));
|
|
1056
|
+
return;
|
|
1057
|
+
}
|
|
1058
|
+
if (flags.json) return writeJson(data);
|
|
1059
|
+
print(JSON.stringify(data, null, 2));
|
|
1060
|
+
return;
|
|
1061
|
+
}
|
|
1062
|
+
|
|
1063
|
+
if (subcommand === 'pull') {
|
|
1064
|
+
const [automationKey] = positional;
|
|
1065
|
+
if (!automationKey) fail('用法: openxiangda automation pull <automationCode|automationId>');
|
|
1066
|
+
const target = getWorkspaceTarget(config, profileName, flags);
|
|
1067
|
+
const automationId = resolveAutomationId(target.bound, automationKey, flags);
|
|
1068
|
+
const data = await requestWithAuth(
|
|
1069
|
+
config,
|
|
1070
|
+
target.profileName,
|
|
1071
|
+
`/openxiangda-api/v1/apps/${encodeURIComponent(target.appType)}/automations/${encodeURIComponent(automationId)}`
|
|
1072
|
+
);
|
|
1073
|
+
if (flags.json) return writeJson(data);
|
|
1074
|
+
print(JSON.stringify(data, null, 2));
|
|
1075
|
+
return;
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1078
|
+
if (['publish', 'unpublish', 'enable', 'disable'].includes(subcommand)) {
|
|
1079
|
+
const [automationKey] = positional;
|
|
1080
|
+
if (!automationKey) {
|
|
1081
|
+
fail(`用法: openxiangda automation ${subcommand} <automationCode|automationId>`);
|
|
1082
|
+
}
|
|
1083
|
+
const target = getWorkspaceTarget(config, profileName, flags);
|
|
1084
|
+
const automationId = resolveAutomationId(target.bound, automationKey, flags);
|
|
1085
|
+
const data = await requestWithAuth(
|
|
1086
|
+
config,
|
|
1087
|
+
target.profileName,
|
|
1088
|
+
`/openxiangda-api/v1/apps/${encodeURIComponent(target.appType)}/automations/${encodeURIComponent(automationId)}/${subcommand}`,
|
|
1089
|
+
{ method: 'POST' }
|
|
1090
|
+
);
|
|
1091
|
+
if (flags.json) return writeJson(data);
|
|
1092
|
+
print(JSON.stringify(data, null, 2));
|
|
1093
|
+
return;
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1096
|
+
if (subcommand === 'delete') {
|
|
1097
|
+
const [automationKey] = positional;
|
|
1098
|
+
if (!automationKey) fail('用法: openxiangda automation delete <automationCode|automationId>');
|
|
1099
|
+
const target = getWorkspaceTarget(config, profileName, flags);
|
|
1100
|
+
const automationId = resolveAutomationId(target.bound, automationKey, flags);
|
|
1101
|
+
const data = await requestWithAuth(
|
|
1102
|
+
config,
|
|
1103
|
+
target.profileName,
|
|
1104
|
+
`/openxiangda-api/v1/apps/${encodeURIComponent(target.appType)}/automations/${encodeURIComponent(automationId)}`,
|
|
1105
|
+
{ method: 'DELETE' }
|
|
1106
|
+
);
|
|
1107
|
+
if (target.bound.resources?.automations?.[automationKey]) {
|
|
1108
|
+
delete target.bound.resources.automations[automationKey];
|
|
1109
|
+
saveProjectState(target.state);
|
|
1110
|
+
}
|
|
1111
|
+
if (flags.json) return writeJson(data);
|
|
1112
|
+
print(JSON.stringify(data, null, 2));
|
|
1113
|
+
return;
|
|
1114
|
+
}
|
|
1115
|
+
|
|
1116
|
+
if (subcommand === 'validate') {
|
|
1117
|
+
const target = getWorkspaceTarget(config, profileName, flags);
|
|
1118
|
+
if (!flags['definition-json']) {
|
|
1119
|
+
fail('用法: openxiangda automation validate --definition-json <file> [--trigger-json <file>]');
|
|
1120
|
+
}
|
|
1121
|
+
const definitionJson = await readDefinitionJsonArg(
|
|
1122
|
+
config,
|
|
1123
|
+
target.profileName,
|
|
1124
|
+
flags['definition-json'],
|
|
1125
|
+
'definition-json'
|
|
1126
|
+
);
|
|
1127
|
+
const data = await requestWithAuth(
|
|
1128
|
+
config,
|
|
1129
|
+
target.profileName,
|
|
1130
|
+
`/openxiangda-api/v1/apps/${encodeURIComponent(target.appType)}/automations/definition/validate`,
|
|
1131
|
+
{
|
|
1132
|
+
method: 'POST',
|
|
1133
|
+
body: {
|
|
1134
|
+
definitionJson,
|
|
1135
|
+
triggerConfig: flags['trigger-json']
|
|
1136
|
+
? readJsonArg(flags['trigger-json'], 'trigger-json')
|
|
1137
|
+
: undefined,
|
|
1138
|
+
strictLiveBinding: Boolean(flags.strict),
|
|
1139
|
+
},
|
|
1140
|
+
}
|
|
1141
|
+
);
|
|
1142
|
+
if (flags.json) return writeJson(data);
|
|
1143
|
+
print(JSON.stringify(data, null, 2));
|
|
1144
|
+
return;
|
|
1145
|
+
}
|
|
1146
|
+
|
|
1147
|
+
if (subcommand === 'cron-validate') {
|
|
1148
|
+
const target = getWorkspaceTarget(config, profileName, flags);
|
|
1149
|
+
const cronExpression = flags.cron || positional[0];
|
|
1150
|
+
if (!cronExpression) fail('用法: openxiangda automation cron-validate <cron>');
|
|
1151
|
+
const data = await requestWithAuth(
|
|
1152
|
+
config,
|
|
1153
|
+
target.profileName,
|
|
1154
|
+
`/openxiangda-api/v1/apps/${encodeURIComponent(target.appType)}/automations/cron/validate`,
|
|
1155
|
+
{
|
|
1156
|
+
method: 'POST',
|
|
1157
|
+
body: { cronExpression },
|
|
1158
|
+
}
|
|
1159
|
+
);
|
|
1160
|
+
if (flags.json) return writeJson(data);
|
|
1161
|
+
print(JSON.stringify(data, null, 2));
|
|
1162
|
+
return;
|
|
1163
|
+
}
|
|
1164
|
+
|
|
1165
|
+
fail('用法: openxiangda automation list|create|bind|pull|publish|unpublish|enable|disable|delete|validate|cron-validate');
|
|
1166
|
+
}
|
|
1167
|
+
|
|
1168
|
+
async function permission(args) {
|
|
1169
|
+
const [subcommand, ...rest] = args;
|
|
1170
|
+
const { flags, positional } = parseArgs(rest);
|
|
1171
|
+
const config = loadConfig();
|
|
1172
|
+
const profileName = flags.profile || config.currentProfile;
|
|
1173
|
+
|
|
1174
|
+
if (subcommand === 'role-list') {
|
|
1175
|
+
const target = getWorkspaceTarget(config, profileName, flags);
|
|
1176
|
+
const data = await requestWithAuth(
|
|
1177
|
+
config,
|
|
1178
|
+
target.profileName,
|
|
1179
|
+
apiPathWithQuery(
|
|
1180
|
+
`/openxiangda-api/v1/apps/${encodeURIComponent(target.appType)}/roles`,
|
|
1181
|
+
{
|
|
1182
|
+
name: flags.name,
|
|
1183
|
+
code: flags.code,
|
|
1184
|
+
page: flags.page,
|
|
1185
|
+
limit: flags.limit,
|
|
1186
|
+
}
|
|
1187
|
+
)
|
|
1188
|
+
);
|
|
1189
|
+
if (flags.json) return writeJson(data);
|
|
1190
|
+
print(JSON.stringify(data, null, 2));
|
|
1191
|
+
return;
|
|
1192
|
+
}
|
|
1193
|
+
|
|
1194
|
+
if (subcommand === 'role-bind') {
|
|
1195
|
+
const [roleCode, maybeRoleId] = positional;
|
|
1196
|
+
const roleId = flags['role-id'] || maybeRoleId;
|
|
1197
|
+
if (!roleCode || !roleId) {
|
|
1198
|
+
fail('用法: openxiangda permission role-bind <roleCode> --role-id <id>');
|
|
1199
|
+
}
|
|
1200
|
+
const target = getWorkspaceTarget(config, profileName, flags);
|
|
1201
|
+
saveRoleResource(target, roleCode, roleId);
|
|
1202
|
+
print(`已绑定角色 ${roleCode}: ${roleId}`);
|
|
1203
|
+
return;
|
|
1204
|
+
}
|
|
1205
|
+
|
|
1206
|
+
if (subcommand === 'role-create') {
|
|
1207
|
+
const [roleCode] = positional;
|
|
1208
|
+
const name = flags.name || positional.slice(1).join(' ') || roleCode;
|
|
1209
|
+
if (!roleCode || !name) {
|
|
1210
|
+
fail('用法: openxiangda permission role-create <roleCode> --name <text>');
|
|
1211
|
+
}
|
|
1212
|
+
const target = getWorkspaceTarget(config, profileName, flags);
|
|
1213
|
+
const data = await requestWithAuth(
|
|
1214
|
+
config,
|
|
1215
|
+
target.profileName,
|
|
1216
|
+
`/openxiangda-api/v1/apps/${encodeURIComponent(target.appType)}/roles`,
|
|
1217
|
+
{
|
|
1218
|
+
method: 'POST',
|
|
1219
|
+
body: {
|
|
1220
|
+
code: flags.code || roleCode,
|
|
1221
|
+
name,
|
|
1222
|
+
description: flags.description || '',
|
|
1223
|
+
},
|
|
1224
|
+
}
|
|
1225
|
+
);
|
|
1226
|
+
if (data?.id) saveRoleResource(target, roleCode, data.id, { code: data.code, name: data.name });
|
|
1227
|
+
if (flags.json) return writeJson(data);
|
|
1228
|
+
print(JSON.stringify(data, null, 2));
|
|
1229
|
+
return;
|
|
1230
|
+
}
|
|
1231
|
+
|
|
1232
|
+
if (subcommand === 'role-users') {
|
|
1233
|
+
const [roleKey] = positional;
|
|
1234
|
+
if (!roleKey) fail('用法: openxiangda permission role-users <roleCode|roleId>');
|
|
1235
|
+
const target = getWorkspaceTarget(config, profileName, flags);
|
|
1236
|
+
const roleId = resolveRoleId(target.bound, roleKey, flags);
|
|
1237
|
+
const data = await requestWithAuth(
|
|
1238
|
+
config,
|
|
1239
|
+
target.profileName,
|
|
1240
|
+
apiPathWithQuery(
|
|
1241
|
+
`/openxiangda-api/v1/apps/${encodeURIComponent(target.appType)}/roles/${encodeURIComponent(roleId)}/users`,
|
|
1242
|
+
{
|
|
1243
|
+
keyword: flags.keyword,
|
|
1244
|
+
page: flags.page,
|
|
1245
|
+
limit: flags.limit,
|
|
1246
|
+
}
|
|
1247
|
+
)
|
|
1248
|
+
);
|
|
1249
|
+
if (flags.json) return writeJson(data);
|
|
1250
|
+
print(JSON.stringify(data, null, 2));
|
|
1251
|
+
return;
|
|
1252
|
+
}
|
|
1253
|
+
|
|
1254
|
+
if (subcommand === 'role-add-users') {
|
|
1255
|
+
const [roleKey] = positional;
|
|
1256
|
+
const userIds = splitList(flags['user-ids']);
|
|
1257
|
+
if (!roleKey || userIds.length === 0) {
|
|
1258
|
+
fail('用法: openxiangda permission role-add-users <roleCode|roleId> --user-ids <id1,id2>');
|
|
1259
|
+
}
|
|
1260
|
+
const target = getWorkspaceTarget(config, profileName, flags);
|
|
1261
|
+
const roleId = resolveRoleId(target.bound, roleKey, flags);
|
|
1262
|
+
const data = await requestWithAuth(
|
|
1263
|
+
config,
|
|
1264
|
+
target.profileName,
|
|
1265
|
+
`/openxiangda-api/v1/apps/${encodeURIComponent(target.appType)}/roles/${encodeURIComponent(roleId)}/users`,
|
|
1266
|
+
{
|
|
1267
|
+
method: 'POST',
|
|
1268
|
+
body: { userIds },
|
|
1269
|
+
}
|
|
1270
|
+
);
|
|
1271
|
+
if (flags.json) return writeJson(data);
|
|
1272
|
+
print(JSON.stringify(data, null, 2));
|
|
1273
|
+
return;
|
|
1274
|
+
}
|
|
1275
|
+
|
|
1276
|
+
if (subcommand === 'page-group-list') {
|
|
1277
|
+
const target = getWorkspaceTarget(config, profileName, flags);
|
|
1278
|
+
const data = await requestWithAuth(
|
|
1279
|
+
config,
|
|
1280
|
+
target.profileName,
|
|
1281
|
+
apiPathWithQuery(
|
|
1282
|
+
`/openxiangda-api/v1/apps/${encodeURIComponent(target.appType)}/page-permission-groups`,
|
|
1283
|
+
{
|
|
1284
|
+
name: flags.name,
|
|
1285
|
+
page: flags.page,
|
|
1286
|
+
limit: flags.limit,
|
|
1287
|
+
}
|
|
1288
|
+
)
|
|
1289
|
+
);
|
|
1290
|
+
if (flags.json) return writeJson(data);
|
|
1291
|
+
print(JSON.stringify(data, null, 2));
|
|
1292
|
+
return;
|
|
1293
|
+
}
|
|
1294
|
+
|
|
1295
|
+
if (subcommand === 'page-group-bind') {
|
|
1296
|
+
const [groupCode, maybeGroupId] = positional;
|
|
1297
|
+
const groupId = flags['group-id'] || maybeGroupId;
|
|
1298
|
+
if (!groupCode || !groupId) {
|
|
1299
|
+
fail('用法: openxiangda permission page-group-bind <groupCode> --group-id <id>');
|
|
1300
|
+
}
|
|
1301
|
+
const target = getWorkspaceTarget(config, profileName, flags);
|
|
1302
|
+
savePagePermissionGroupResource(target, groupCode, groupId);
|
|
1303
|
+
print(`已绑定页面权限组 ${groupCode}: ${groupId}`);
|
|
1304
|
+
return;
|
|
1305
|
+
}
|
|
1306
|
+
|
|
1307
|
+
if (subcommand === 'page-group-create') {
|
|
1308
|
+
const [groupCode] = positional;
|
|
1309
|
+
const name = flags.name || positional.slice(1).join(' ') || groupCode;
|
|
1310
|
+
if (!groupCode || !name) {
|
|
1311
|
+
fail('用法: openxiangda permission page-group-create <groupCode> --name <text>');
|
|
1312
|
+
}
|
|
1313
|
+
const target = getWorkspaceTarget(config, profileName, flags);
|
|
1314
|
+
const data = await requestWithAuth(
|
|
1315
|
+
config,
|
|
1316
|
+
target.profileName,
|
|
1317
|
+
`/openxiangda-api/v1/apps/${encodeURIComponent(target.appType)}/page-permission-groups`,
|
|
1318
|
+
{
|
|
1319
|
+
method: 'POST',
|
|
1320
|
+
body: {
|
|
1321
|
+
name,
|
|
1322
|
+
roles: splitList(flags.roles),
|
|
1323
|
+
menuFormUuids: resolveFormUuidList(target.bound, flags['menu-form-uuids'], flags['form-codes']),
|
|
1324
|
+
},
|
|
1325
|
+
}
|
|
1326
|
+
);
|
|
1327
|
+
if (data?.id) savePagePermissionGroupResource(target, groupCode, data.id, { name });
|
|
1328
|
+
if (flags.json) return writeJson(data);
|
|
1329
|
+
print(JSON.stringify(data, null, 2));
|
|
1330
|
+
return;
|
|
1331
|
+
}
|
|
1332
|
+
|
|
1333
|
+
if (subcommand === 'form-group-list') {
|
|
1334
|
+
const target = getWorkspaceTarget(config, profileName, flags);
|
|
1335
|
+
const formUuid = flags['form-uuid'] || resolveOptionalFormUuid(target.bound, flags['form-code']);
|
|
1336
|
+
if (!formUuid) {
|
|
1337
|
+
fail('用法: openxiangda permission form-group-list --form-code <formCode>|--form-uuid <FORM_XXX>');
|
|
1338
|
+
}
|
|
1339
|
+
const data = await requestWithAuth(
|
|
1340
|
+
config,
|
|
1341
|
+
target.profileName,
|
|
1342
|
+
apiPathWithQuery(
|
|
1343
|
+
`/openxiangda-api/v1/apps/${encodeURIComponent(target.appType)}/forms/${encodeURIComponent(formUuid)}/permission-groups`,
|
|
1344
|
+
{
|
|
1345
|
+
type: flags.type,
|
|
1346
|
+
name: flags.name,
|
|
1347
|
+
page: flags.page,
|
|
1348
|
+
limit: flags.limit,
|
|
1349
|
+
}
|
|
1350
|
+
)
|
|
1351
|
+
);
|
|
1352
|
+
if (flags.json) return writeJson(data);
|
|
1353
|
+
print(JSON.stringify(data, null, 2));
|
|
1354
|
+
return;
|
|
1355
|
+
}
|
|
1356
|
+
|
|
1357
|
+
if (subcommand === 'form-group-bind') {
|
|
1358
|
+
const [groupCode, maybeGroupId] = positional;
|
|
1359
|
+
const groupId = flags['group-id'] || maybeGroupId;
|
|
1360
|
+
if (!groupCode || !groupId) {
|
|
1361
|
+
fail('用法: openxiangda permission form-group-bind <groupCode> --group-id <id>');
|
|
1362
|
+
}
|
|
1363
|
+
const target = getWorkspaceTarget(config, profileName, flags);
|
|
1364
|
+
const formUuid = flags['form-uuid'] || resolveOptionalFormUuid(target.bound, flags['form-code']);
|
|
1365
|
+
saveFormPermissionGroupResource(target, groupCode, groupId, { formUuid });
|
|
1366
|
+
print(`已绑定表单权限组 ${groupCode}: ${groupId}`);
|
|
1367
|
+
return;
|
|
1368
|
+
}
|
|
1369
|
+
|
|
1370
|
+
if (subcommand === 'form-group-create') {
|
|
1371
|
+
const [groupCode] = positional;
|
|
1372
|
+
const name = flags.name || positional.slice(1).join(' ') || groupCode;
|
|
1373
|
+
const target = getWorkspaceTarget(config, profileName, flags);
|
|
1374
|
+
const formUuid = flags['form-uuid'] || resolveOptionalFormUuid(target.bound, flags['form-code']);
|
|
1375
|
+
if (!groupCode || !name || !formUuid) {
|
|
1376
|
+
fail('用法: openxiangda permission form-group-create <groupCode> --form-code <formCode>|--form-uuid <FORM_XXX> --name <text> --type <submit|view>');
|
|
1377
|
+
}
|
|
1378
|
+
const data = await requestWithAuth(
|
|
1379
|
+
config,
|
|
1380
|
+
target.profileName,
|
|
1381
|
+
`/openxiangda-api/v1/apps/${encodeURIComponent(target.appType)}/forms/${encodeURIComponent(formUuid)}/permission-groups`,
|
|
1382
|
+
{
|
|
1383
|
+
method: 'POST',
|
|
1384
|
+
body: {
|
|
1385
|
+
name,
|
|
1386
|
+
type: flags.type || 'view',
|
|
1387
|
+
roles: splitList(flags.roles),
|
|
1388
|
+
operations: splitList(flags.operations),
|
|
1389
|
+
...(flags['data-scope-json']
|
|
1390
|
+
? { dataScope: readJsonArg(flags['data-scope-json'], 'data-scope-json') }
|
|
1391
|
+
: {}),
|
|
1392
|
+
...(flags['field-permissions-json']
|
|
1393
|
+
? {
|
|
1394
|
+
fieldPermissions: readJsonArg(
|
|
1395
|
+
flags['field-permissions-json'],
|
|
1396
|
+
'field-permissions-json'
|
|
1397
|
+
),
|
|
1398
|
+
}
|
|
1399
|
+
: {}),
|
|
1400
|
+
...(flags['data-permission-json']
|
|
1401
|
+
? {
|
|
1402
|
+
dataPermission: readJsonArg(
|
|
1403
|
+
flags['data-permission-json'],
|
|
1404
|
+
'data-permission-json'
|
|
1405
|
+
),
|
|
1406
|
+
}
|
|
1407
|
+
: {}),
|
|
1408
|
+
},
|
|
1409
|
+
}
|
|
1410
|
+
);
|
|
1411
|
+
if (data?.id) {
|
|
1412
|
+
saveFormPermissionGroupResource(target, groupCode, data.id, {
|
|
1413
|
+
name,
|
|
1414
|
+
formUuid,
|
|
1415
|
+
});
|
|
1416
|
+
}
|
|
1417
|
+
if (flags.json) return writeJson(data);
|
|
1418
|
+
print(JSON.stringify(data, null, 2));
|
|
1419
|
+
return;
|
|
1420
|
+
}
|
|
1421
|
+
|
|
1422
|
+
if (subcommand === 'form-summary') {
|
|
1423
|
+
const target = getWorkspaceTarget(config, profileName, flags);
|
|
1424
|
+
const formUuid = flags['form-uuid'] || resolveOptionalFormUuid(target.bound, flags['form-code'] || positional[0]);
|
|
1425
|
+
if (!formUuid) fail('用法: openxiangda permission form-summary --form-code <formCode>|--form-uuid <FORM_XXX>');
|
|
1426
|
+
const data = await requestWithAuth(
|
|
1427
|
+
config,
|
|
1428
|
+
target.profileName,
|
|
1429
|
+
`/openxiangda-api/v1/apps/${encodeURIComponent(target.appType)}/forms/${encodeURIComponent(formUuid)}/permission-summary`
|
|
1430
|
+
);
|
|
1431
|
+
if (flags.json) return writeJson(data);
|
|
1432
|
+
print(JSON.stringify(data, null, 2));
|
|
1433
|
+
return;
|
|
1434
|
+
}
|
|
1435
|
+
|
|
1436
|
+
if (subcommand === 'menu-permissions') {
|
|
1437
|
+
const target = getWorkspaceTarget(config, profileName, flags);
|
|
1438
|
+
const data = await requestWithAuth(
|
|
1439
|
+
config,
|
|
1440
|
+
target.profileName,
|
|
1441
|
+
`/openxiangda-api/v1/apps/${encodeURIComponent(target.appType)}/page-permission-groups/user-menu-permissions`
|
|
1442
|
+
);
|
|
1443
|
+
if (flags.json) return writeJson(data);
|
|
1444
|
+
print(JSON.stringify(data, null, 2));
|
|
1445
|
+
return;
|
|
1446
|
+
}
|
|
1447
|
+
|
|
1448
|
+
fail(
|
|
1449
|
+
'用法: 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'
|
|
1450
|
+
);
|
|
1451
|
+
}
|
|
1452
|
+
|
|
1453
|
+
async function settings(args) {
|
|
1454
|
+
const [subcommand, ...rest] = args;
|
|
1455
|
+
const { flags, positional } = parseArgs(rest);
|
|
1456
|
+
const config = loadConfig();
|
|
1457
|
+
const profileName = flags.profile || config.currentProfile;
|
|
1458
|
+
|
|
1459
|
+
if (subcommand === 'get') {
|
|
1460
|
+
const target = getWorkspaceTarget(config, profileName, flags);
|
|
1461
|
+
const formUuid = resolveSettingsFormUuid(target.bound, positional[0], flags);
|
|
1462
|
+
const data = await requestWithAuth(
|
|
1463
|
+
config,
|
|
1464
|
+
target.profileName,
|
|
1465
|
+
`/openxiangda-api/v1/apps/${encodeURIComponent(target.appType)}/forms/${encodeURIComponent(formUuid)}/settings`
|
|
1466
|
+
);
|
|
1467
|
+
if (flags.json) return writeJson(data);
|
|
1468
|
+
print(JSON.stringify(data, null, 2));
|
|
1469
|
+
return;
|
|
1470
|
+
}
|
|
1471
|
+
|
|
1472
|
+
if (subcommand === 'save') {
|
|
1473
|
+
const target = getWorkspaceTarget(config, profileName, flags);
|
|
1474
|
+
const formUuid = resolveSettingsFormUuid(target.bound, positional[0], flags);
|
|
1475
|
+
if (!flags['settings-json']) {
|
|
1476
|
+
fail('用法: openxiangda settings save <formCode|formUuid> --settings-json <file>');
|
|
1477
|
+
}
|
|
1478
|
+
const data = await requestWithAuth(
|
|
1479
|
+
config,
|
|
1480
|
+
target.profileName,
|
|
1481
|
+
`/openxiangda-api/v1/apps/${encodeURIComponent(target.appType)}/forms/${encodeURIComponent(formUuid)}/settings`,
|
|
1482
|
+
{
|
|
1483
|
+
method: 'PUT',
|
|
1484
|
+
body: {
|
|
1485
|
+
settings: readJsonArg(flags['settings-json'], 'settings-json'),
|
|
1486
|
+
},
|
|
1487
|
+
}
|
|
1488
|
+
);
|
|
1489
|
+
if (flags.json) return writeJson(data);
|
|
1490
|
+
print(JSON.stringify(data, null, 2));
|
|
1491
|
+
return;
|
|
1492
|
+
}
|
|
1493
|
+
|
|
1494
|
+
if (subcommand === 'indexes') {
|
|
1495
|
+
const target = getWorkspaceTarget(config, profileName, flags);
|
|
1496
|
+
const formUuid = resolveSettingsFormUuid(target.bound, positional[0], flags);
|
|
1497
|
+
const data = await requestWithAuth(
|
|
1498
|
+
config,
|
|
1499
|
+
target.profileName,
|
|
1500
|
+
`/openxiangda-api/v1/apps/${encodeURIComponent(target.appType)}/forms/${encodeURIComponent(formUuid)}/field-indexes`
|
|
1501
|
+
);
|
|
1502
|
+
if (flags.json) return writeJson(data);
|
|
1503
|
+
print(JSON.stringify(data, null, 2));
|
|
1504
|
+
return;
|
|
1505
|
+
}
|
|
1506
|
+
|
|
1507
|
+
if (subcommand === 'indexes-save') {
|
|
1508
|
+
const target = getWorkspaceTarget(config, profileName, flags);
|
|
1509
|
+
const formUuid = resolveSettingsFormUuid(target.bound, positional[0], flags);
|
|
1510
|
+
if (!flags['indexes-json']) {
|
|
1511
|
+
fail('用法: openxiangda settings indexes-save <formCode|formUuid> --indexes-json <file>');
|
|
1512
|
+
}
|
|
1513
|
+
const data = await requestWithAuth(
|
|
1514
|
+
config,
|
|
1515
|
+
target.profileName,
|
|
1516
|
+
`/openxiangda-api/v1/apps/${encodeURIComponent(target.appType)}/forms/${encodeURIComponent(formUuid)}/field-indexes`,
|
|
1517
|
+
{
|
|
1518
|
+
method: 'PUT',
|
|
1519
|
+
body: {
|
|
1520
|
+
indexes: readJsonArg(flags['indexes-json'], 'indexes-json'),
|
|
1521
|
+
},
|
|
1522
|
+
}
|
|
1523
|
+
);
|
|
1524
|
+
if (flags.json) return writeJson(data);
|
|
1525
|
+
print(JSON.stringify(data, null, 2));
|
|
1526
|
+
return;
|
|
1527
|
+
}
|
|
1528
|
+
|
|
1529
|
+
if (subcommand === 'data-management') {
|
|
1530
|
+
const target = getWorkspaceTarget(config, profileName, flags);
|
|
1531
|
+
const formUuid = resolveSettingsFormUuid(target.bound, positional[0], flags);
|
|
1532
|
+
const data = await requestWithAuth(
|
|
1533
|
+
config,
|
|
1534
|
+
target.profileName,
|
|
1535
|
+
`/openxiangda-api/v1/apps/${encodeURIComponent(target.appType)}/forms/${encodeURIComponent(formUuid)}/data-management`
|
|
1536
|
+
);
|
|
1537
|
+
if (flags.json) return writeJson(data);
|
|
1538
|
+
print(JSON.stringify(data, null, 2));
|
|
1539
|
+
return;
|
|
1540
|
+
}
|
|
1541
|
+
|
|
1542
|
+
if (subcommand === 'data-management-save') {
|
|
1543
|
+
const target = getWorkspaceTarget(config, profileName, flags);
|
|
1544
|
+
const formUuid = resolveSettingsFormUuid(target.bound, positional[0], flags);
|
|
1545
|
+
if (!flags['config-json']) {
|
|
1546
|
+
fail('用法: openxiangda settings data-management-save <formCode|formUuid> --config-json <file>');
|
|
1547
|
+
}
|
|
1548
|
+
const data = await requestWithAuth(
|
|
1549
|
+
config,
|
|
1550
|
+
target.profileName,
|
|
1551
|
+
`/openxiangda-api/v1/apps/${encodeURIComponent(target.appType)}/forms/${encodeURIComponent(formUuid)}/data-management`,
|
|
1552
|
+
{
|
|
1553
|
+
method: 'PUT',
|
|
1554
|
+
body: {
|
|
1555
|
+
config: readJsonArg(flags['config-json'], 'config-json'),
|
|
1556
|
+
},
|
|
1557
|
+
}
|
|
1558
|
+
);
|
|
1559
|
+
if (flags.json) return writeJson(data);
|
|
1560
|
+
print(JSON.stringify(data, null, 2));
|
|
1561
|
+
return;
|
|
1562
|
+
}
|
|
1563
|
+
|
|
1564
|
+
if (subcommand === 'public-access') {
|
|
1565
|
+
const target = getWorkspaceTarget(config, profileName, flags);
|
|
1566
|
+
const formUuid = resolveSettingsFormUuid(target.bound, positional[0], flags);
|
|
1567
|
+
const data = await requestWithAuth(
|
|
1568
|
+
config,
|
|
1569
|
+
target.profileName,
|
|
1570
|
+
`/openxiangda-api/v1/apps/${encodeURIComponent(target.appType)}/forms/${encodeURIComponent(formUuid)}/public-access`
|
|
1571
|
+
);
|
|
1572
|
+
if (flags.json) return writeJson(data);
|
|
1573
|
+
print(JSON.stringify(data, null, 2));
|
|
1574
|
+
return;
|
|
1575
|
+
}
|
|
1576
|
+
|
|
1577
|
+
if (subcommand === 'public-access-save') {
|
|
1578
|
+
const target = getWorkspaceTarget(config, profileName, flags);
|
|
1579
|
+
const formUuid = resolveSettingsFormUuid(target.bound, positional[0], flags);
|
|
1580
|
+
const data = await requestWithAuth(
|
|
1581
|
+
config,
|
|
1582
|
+
target.profileName,
|
|
1583
|
+
`/openxiangda-api/v1/apps/${encodeURIComponent(target.appType)}/forms/${encodeURIComponent(formUuid)}/public-access`,
|
|
1584
|
+
{
|
|
1585
|
+
method: 'PUT',
|
|
1586
|
+
body: {
|
|
1587
|
+
isPublic: flags.public === undefined ? true : String(flags.public) !== 'false',
|
|
1588
|
+
description: flags.description || '',
|
|
1589
|
+
},
|
|
1590
|
+
}
|
|
1591
|
+
);
|
|
1592
|
+
if (flags.json) return writeJson(data);
|
|
1593
|
+
print(JSON.stringify(data, null, 2));
|
|
1594
|
+
return;
|
|
1595
|
+
}
|
|
1596
|
+
|
|
1597
|
+
if (subcommand === 'public-access-delete') {
|
|
1598
|
+
const target = getWorkspaceTarget(config, profileName, flags);
|
|
1599
|
+
const formUuid = resolveSettingsFormUuid(target.bound, positional[0], flags);
|
|
1600
|
+
const data = await requestWithAuth(
|
|
1601
|
+
config,
|
|
1602
|
+
target.profileName,
|
|
1603
|
+
`/openxiangda-api/v1/apps/${encodeURIComponent(target.appType)}/forms/${encodeURIComponent(formUuid)}/public-access`,
|
|
1604
|
+
{ method: 'DELETE' }
|
|
1605
|
+
);
|
|
1606
|
+
if (flags.json) return writeJson(data);
|
|
1607
|
+
print(JSON.stringify(data, null, 2));
|
|
1608
|
+
return;
|
|
1609
|
+
}
|
|
1610
|
+
|
|
1611
|
+
fail(
|
|
1612
|
+
'用法: openxiangda settings get|save|indexes|indexes-save|data-management|data-management-save|public-access|public-access-save|public-access-delete'
|
|
1613
|
+
);
|
|
1614
|
+
}
|
|
1615
|
+
|
|
1616
|
+
async function inspect(args) {
|
|
1617
|
+
const [subcommand, ...rest] = args;
|
|
1618
|
+
const { flags, positional } = parseArgs(rest);
|
|
1619
|
+
const config = loadConfig();
|
|
1620
|
+
const profileName = flags.profile || config.currentProfile;
|
|
1621
|
+
|
|
1622
|
+
if (!subcommand || subcommand === 'app' || subcommand === 'snapshot') {
|
|
1623
|
+
const target = getWorkspaceTarget(config, profileName, flags);
|
|
1624
|
+
const appType = positional[0] || target.appType;
|
|
1625
|
+
const data = await requestWithAuth(
|
|
1626
|
+
config,
|
|
1627
|
+
target.profileName,
|
|
1628
|
+
`/openxiangda-api/v1/apps/${encodeURIComponent(appType)}/snapshot`
|
|
1629
|
+
);
|
|
1630
|
+
if (flags.json) return writeJson(data);
|
|
1631
|
+
print(JSON.stringify(data, null, 2));
|
|
1632
|
+
return;
|
|
1633
|
+
}
|
|
1634
|
+
|
|
1635
|
+
if (subcommand === 'form') {
|
|
1636
|
+
const target = getWorkspaceTarget(config, profileName, flags);
|
|
1637
|
+
const formUuid = resolveSettingsFormUuid(target.bound, positional[0], flags);
|
|
1638
|
+
const data = await requestWithAuth(
|
|
1639
|
+
config,
|
|
1640
|
+
target.profileName,
|
|
1641
|
+
`/openxiangda-api/v1/apps/${encodeURIComponent(target.appType)}/forms/${encodeURIComponent(formUuid)}`
|
|
1642
|
+
);
|
|
1643
|
+
if (flags.json) return writeJson(data);
|
|
1644
|
+
print(JSON.stringify(data, null, 2));
|
|
1645
|
+
return;
|
|
1646
|
+
}
|
|
1647
|
+
|
|
1648
|
+
if (subcommand === 'workflow') {
|
|
1649
|
+
const [workflowKey] = positional;
|
|
1650
|
+
if (!workflowKey) fail('用法: openxiangda inspect workflow <workflowCode|workflowId>');
|
|
1651
|
+
const target = getWorkspaceTarget(config, profileName, flags);
|
|
1652
|
+
const workflowId = resolveWorkflowId(target.bound, workflowKey, flags);
|
|
1653
|
+
const data = await requestWithAuth(
|
|
1654
|
+
config,
|
|
1655
|
+
target.profileName,
|
|
1656
|
+
`/openxiangda-api/v1/apps/${encodeURIComponent(target.appType)}/workflows/${encodeURIComponent(workflowId)}`
|
|
1657
|
+
);
|
|
1658
|
+
if (flags.json) return writeJson(data);
|
|
1659
|
+
print(JSON.stringify(data, null, 2));
|
|
1660
|
+
return;
|
|
1661
|
+
}
|
|
1662
|
+
|
|
1663
|
+
if (subcommand === 'automation') {
|
|
1664
|
+
const [automationKey] = positional;
|
|
1665
|
+
if (!automationKey) fail('用法: openxiangda inspect automation <automationCode|automationId>');
|
|
1666
|
+
const target = getWorkspaceTarget(config, profileName, flags);
|
|
1667
|
+
const automationId = resolveAutomationId(target.bound, automationKey, flags);
|
|
1668
|
+
const data = await requestWithAuth(
|
|
1669
|
+
config,
|
|
1670
|
+
target.profileName,
|
|
1671
|
+
`/openxiangda-api/v1/apps/${encodeURIComponent(target.appType)}/automations/${encodeURIComponent(automationId)}`
|
|
1672
|
+
);
|
|
1673
|
+
if (flags.json) return writeJson(data);
|
|
1674
|
+
print(JSON.stringify(data, null, 2));
|
|
1675
|
+
return;
|
|
1676
|
+
}
|
|
1677
|
+
|
|
1678
|
+
if (subcommand === 'permissions') {
|
|
1679
|
+
const target = getWorkspaceTarget(config, profileName, flags);
|
|
1680
|
+
const formUuid = resolveSettingsFormUuid(target.bound, positional[0], flags);
|
|
1681
|
+
const data = await requestWithAuth(
|
|
1682
|
+
config,
|
|
1683
|
+
target.profileName,
|
|
1684
|
+
`/openxiangda-api/v1/apps/${encodeURIComponent(target.appType)}/forms/${encodeURIComponent(formUuid)}/permission-summary`
|
|
1685
|
+
);
|
|
1686
|
+
if (flags.json) return writeJson(data);
|
|
1687
|
+
print(JSON.stringify(data, null, 2));
|
|
1688
|
+
return;
|
|
1689
|
+
}
|
|
1690
|
+
|
|
1691
|
+
fail('用法: openxiangda inspect app|form|workflow|automation|permissions');
|
|
1692
|
+
}
|
|
1693
|
+
|
|
1694
|
+
async function commands(args) {
|
|
1695
|
+
const { flags } = parseArgs(args);
|
|
1696
|
+
const manifest = {
|
|
1697
|
+
name: 'openxiangda',
|
|
1698
|
+
commands: [
|
|
1699
|
+
'login <platform-url> [--profile name]',
|
|
1700
|
+
'platform add|list|use|remove',
|
|
1701
|
+
'auth status|refresh|logout',
|
|
1702
|
+
'env',
|
|
1703
|
+
'workspace init|bind|publish',
|
|
1704
|
+
'app list|create|snapshot',
|
|
1705
|
+
'form list|create|bind|pull|publish',
|
|
1706
|
+
'page list|publish|bind|releases|activate',
|
|
1707
|
+
'menu list|create|bind|delete',
|
|
1708
|
+
'workflow list|create|bind|pull|publish|delete|validate',
|
|
1709
|
+
'automation list|create|bind|pull|publish|unpublish|enable|disable|delete|validate|cron-validate',
|
|
1710
|
+
'permission role-list|role-create|role-bind|role-users|role-add-users',
|
|
1711
|
+
'permission page-group-list|page-group-create|page-group-bind',
|
|
1712
|
+
'permission form-group-list|form-group-create|form-group-bind|form-summary|menu-permissions',
|
|
1713
|
+
'settings get|save|indexes|indexes-save|data-management|data-management-save|public-access|public-access-save|public-access-delete',
|
|
1714
|
+
'inspect app|form|workflow|automation|permissions',
|
|
1715
|
+
'skill install|status',
|
|
1716
|
+
],
|
|
1717
|
+
};
|
|
1718
|
+
if (flags.json) return writeJson(manifest);
|
|
1719
|
+
print(JSON.stringify(manifest, null, 2));
|
|
1720
|
+
}
|
|
1721
|
+
|
|
1722
|
+
function printWorkspaceInitReport(result) {
|
|
1723
|
+
const lines = [
|
|
1724
|
+
`已创建 OpenXiangda workspace: ${result.targetDir}`,
|
|
1725
|
+
`package: ${result.packageName}`,
|
|
1726
|
+
];
|
|
1727
|
+
if (result.bound) {
|
|
1728
|
+
lines.push(`已绑定 ${result.bound.profile}: ${result.bound.appType}`);
|
|
1729
|
+
}
|
|
1730
|
+
if (result.installedDependencies) {
|
|
1731
|
+
lines.push('依赖已安装');
|
|
1732
|
+
}
|
|
1733
|
+
lines.push('下一步:');
|
|
1734
|
+
for (const step of result.nextSteps) {
|
|
1735
|
+
lines.push(` ${step}`);
|
|
1736
|
+
}
|
|
1737
|
+
print(lines.join('\n'));
|
|
1738
|
+
}
|
|
1739
|
+
|
|
1740
|
+
async function skill(args) {
|
|
1741
|
+
const [subcommand, ...rest] = args;
|
|
1742
|
+
const { flags } = parseArgs(rest);
|
|
1743
|
+
const options = {
|
|
1744
|
+
agent: flags.agent || 'codex',
|
|
1745
|
+
dest: flags.dest,
|
|
1746
|
+
force: Boolean(flags.force),
|
|
1747
|
+
dryRun: Boolean(flags['dry-run']),
|
|
1748
|
+
};
|
|
1749
|
+
|
|
1750
|
+
if (subcommand === 'install') {
|
|
1751
|
+
const result = installSkills(options);
|
|
1752
|
+
if (flags.json) return writeJson(result);
|
|
1753
|
+
printSkillInstallReport(result);
|
|
1754
|
+
return;
|
|
1755
|
+
}
|
|
1756
|
+
|
|
1757
|
+
if (subcommand === 'status') {
|
|
1758
|
+
const result = getSkillStatusReport(options);
|
|
1759
|
+
if (flags.json) return writeJson(result);
|
|
1760
|
+
printSkillStatusReport(result);
|
|
1761
|
+
return;
|
|
1762
|
+
}
|
|
1763
|
+
|
|
1764
|
+
fail('用法: openxiangda skill install|status [--agent codex] [--dest <skills-dir>]');
|
|
1765
|
+
}
|
|
1766
|
+
|
|
1767
|
+
function printSkillInstallReport(result) {
|
|
1768
|
+
if (result.results) {
|
|
1769
|
+
// 多平台安装结果
|
|
1770
|
+
print(`OpenXiangda skill install (${result.agent})`);
|
|
1771
|
+
for (const res of result.results) {
|
|
1772
|
+
print(`Destination: ${res.skillsDir}`);
|
|
1773
|
+
for (const item of res.operations) {
|
|
1774
|
+
print(`- ${item.name}: ${item.operation}`);
|
|
1775
|
+
}
|
|
1776
|
+
}
|
|
1777
|
+
} else {
|
|
1778
|
+
// 单平台安装结果
|
|
1779
|
+
const lines = [
|
|
1780
|
+
`OpenXiangda skill install (${result.agent})`,
|
|
1781
|
+
`Destination: ${result.skillsDir}`,
|
|
1782
|
+
];
|
|
1783
|
+
for (const item of result.operations) {
|
|
1784
|
+
lines.push(`- ${item.name}: ${item.operation}`);
|
|
1785
|
+
}
|
|
1786
|
+
for (const warning of result.warnings) {
|
|
1787
|
+
lines.push(`Warning: ${warning}`);
|
|
1788
|
+
}
|
|
1789
|
+
print(lines.join('\n'));
|
|
1790
|
+
}
|
|
1791
|
+
|
|
1792
|
+
print(result.message);
|
|
1793
|
+
}
|
|
1794
|
+
|
|
1795
|
+
function printSkillStatusReport(result) {
|
|
1796
|
+
if (result.results) {
|
|
1797
|
+
// 多平台状态结果
|
|
1798
|
+
print(`OpenXiangda skill status (${result.agent})`);
|
|
1799
|
+
for (const res of result.results) {
|
|
1800
|
+
print(`Destination: ${res.skillsDir}`);
|
|
1801
|
+
for (const item of res.skills) {
|
|
1802
|
+
const version = item.installedVersion ? ` (${item.installedVersion})` : '';
|
|
1803
|
+
print(`- ${item.name}: ${item.status}${version}`);
|
|
1804
|
+
}
|
|
1805
|
+
}
|
|
1806
|
+
} else {
|
|
1807
|
+
// 单平台状态结果
|
|
1808
|
+
const lines = [
|
|
1809
|
+
`OpenXiangda skill status (${result.agent})`,
|
|
1810
|
+
`Destination: ${result.skillsDir}`,
|
|
1811
|
+
];
|
|
1812
|
+
for (const item of result.skills) {
|
|
1813
|
+
const version = item.installedVersion ? ` (${item.installedVersion})` : '';
|
|
1814
|
+
lines.push(`- ${item.name}: ${item.status}${version}`);
|
|
1815
|
+
}
|
|
1816
|
+
for (const warning of result.warnings) {
|
|
1817
|
+
lines.push(`Warning: ${warning}`);
|
|
1818
|
+
}
|
|
1819
|
+
print(lines.join('\n'));
|
|
1820
|
+
}
|
|
1821
|
+
}
|
|
1822
|
+
|
|
1823
|
+
function getWorkspaceTarget(config, profileName, flags = {}) {
|
|
1824
|
+
const resolved = getProfile(config, profileName);
|
|
1825
|
+
const state = loadProjectState();
|
|
1826
|
+
const bound = state.profiles?.[resolved.profileName] || {};
|
|
1827
|
+
const appType = flags['app-type'] || bound.appType;
|
|
1828
|
+
if (!appType) {
|
|
1829
|
+
fail(
|
|
1830
|
+
`profile ${resolved.profileName} 未绑定应用。先执行 openxiangda workspace bind --profile ${resolved.profileName} --app-type APP_XXX`
|
|
1831
|
+
);
|
|
1832
|
+
}
|
|
1833
|
+
return {
|
|
1834
|
+
profileName: resolved.profileName,
|
|
1835
|
+
profile: resolved.profile,
|
|
1836
|
+
state,
|
|
1837
|
+
bound,
|
|
1838
|
+
appType,
|
|
1839
|
+
};
|
|
1840
|
+
}
|
|
1841
|
+
|
|
1842
|
+
function ensureResourceBuckets(bound) {
|
|
1843
|
+
bound.resources = bound.resources || {};
|
|
1844
|
+
bound.resources.forms = bound.resources.forms || {};
|
|
1845
|
+
bound.resources.pages = bound.resources.pages || {};
|
|
1846
|
+
bound.resources.workflows = bound.resources.workflows || {};
|
|
1847
|
+
bound.resources.automations = bound.resources.automations || {};
|
|
1848
|
+
bound.resources.menus = bound.resources.menus || {};
|
|
1849
|
+
bound.resources.roles = bound.resources.roles || {};
|
|
1850
|
+
bound.resources.pagePermissionGroups = bound.resources.pagePermissionGroups || {};
|
|
1851
|
+
bound.resources.formPermissionGroups = bound.resources.formPermissionGroups || {};
|
|
1852
|
+
}
|
|
1853
|
+
|
|
1854
|
+
function resolveFormUuid(bound, formKey, flags = {}) {
|
|
1855
|
+
if (flags['form-uuid']) return flags['form-uuid'];
|
|
1856
|
+
const mapped = bound.resources?.forms?.[formKey]?.formUuid;
|
|
1857
|
+
return mapped || formKey;
|
|
1858
|
+
}
|
|
1859
|
+
|
|
1860
|
+
function resolveOptionalFormUuid(bound, formCode) {
|
|
1861
|
+
if (!formCode) return undefined;
|
|
1862
|
+
return bound.resources?.forms?.[formCode]?.formUuid || formCode;
|
|
1863
|
+
}
|
|
1864
|
+
|
|
1865
|
+
function resolveOptionalPageId(bound, pageCode) {
|
|
1866
|
+
if (!pageCode) return undefined;
|
|
1867
|
+
return bound.resources?.pages?.[pageCode]?.pageId || pageCode;
|
|
1868
|
+
}
|
|
1869
|
+
|
|
1870
|
+
function resolveMenuId(bound, menuKey, flags = {}) {
|
|
1871
|
+
if (flags['menu-id']) return flags['menu-id'];
|
|
1872
|
+
const mapped = bound.resources?.menus?.[menuKey]?.menuId;
|
|
1873
|
+
return mapped || menuKey;
|
|
1874
|
+
}
|
|
1875
|
+
|
|
1876
|
+
function resolveWorkflowId(bound, workflowKey, flags = {}) {
|
|
1877
|
+
if (flags['workflow-id']) return flags['workflow-id'];
|
|
1878
|
+
const mapped = bound.resources?.workflows?.[workflowKey]?.workflowId;
|
|
1879
|
+
return mapped || workflowKey;
|
|
1880
|
+
}
|
|
1881
|
+
|
|
1882
|
+
function resolveAutomationId(bound, automationKey, flags = {}) {
|
|
1883
|
+
if (flags['automation-id']) return flags['automation-id'];
|
|
1884
|
+
const mapped = bound.resources?.automations?.[automationKey]?.automationId;
|
|
1885
|
+
return mapped || automationKey;
|
|
1886
|
+
}
|
|
1887
|
+
|
|
1888
|
+
function resolveRoleId(bound, roleKey, flags = {}) {
|
|
1889
|
+
if (flags['role-id']) return flags['role-id'];
|
|
1890
|
+
const mapped = bound.resources?.roles?.[roleKey]?.roleId;
|
|
1891
|
+
return mapped || roleKey;
|
|
1892
|
+
}
|
|
1893
|
+
|
|
1894
|
+
function resolveFormUuidList(bound, rawFormUuids, rawFormCodes) {
|
|
1895
|
+
const direct = splitList(rawFormUuids);
|
|
1896
|
+
const fromCodes = splitList(rawFormCodes).map(code => resolveOptionalFormUuid(bound, code));
|
|
1897
|
+
return [...direct, ...fromCodes].filter(Boolean);
|
|
1898
|
+
}
|
|
1899
|
+
|
|
1900
|
+
function resolveSettingsFormUuid(bound, formKey, flags = {}) {
|
|
1901
|
+
const key = formKey || flags['form-code'] || flags['form-uuid'];
|
|
1902
|
+
if (!key && !flags['form-uuid']) {
|
|
1903
|
+
fail('缺少表单标识。传入 <formCode|formUuid> 或 --form-code/--form-uuid');
|
|
1904
|
+
}
|
|
1905
|
+
return resolveFormUuid(bound, key, flags);
|
|
1906
|
+
}
|
|
1907
|
+
|
|
1908
|
+
function saveFormResource(target, formCode, formUuid, extra = {}) {
|
|
1909
|
+
target.state.profiles = target.state.profiles || {};
|
|
1910
|
+
target.state.profiles[target.profileName] = {
|
|
1911
|
+
...target.bound,
|
|
1912
|
+
baseUrl: target.profile.baseUrl,
|
|
1913
|
+
appType: target.appType,
|
|
1914
|
+
updatedAt: new Date().toISOString(),
|
|
1915
|
+
};
|
|
1916
|
+
const nextBound = target.state.profiles[target.profileName];
|
|
1917
|
+
ensureResourceBuckets(nextBound);
|
|
1918
|
+
nextBound.resources.forms[formCode] = {
|
|
1919
|
+
...(nextBound.resources.forms[formCode] || {}),
|
|
1920
|
+
...extra,
|
|
1921
|
+
formUuid,
|
|
1922
|
+
updatedAt: new Date().toISOString(),
|
|
1923
|
+
};
|
|
1924
|
+
saveProjectState(target.state);
|
|
1925
|
+
target.bound = nextBound;
|
|
1926
|
+
}
|
|
1927
|
+
|
|
1928
|
+
function savePageResource(target, pageCode, pageId, extra = {}) {
|
|
1929
|
+
target.state.profiles = target.state.profiles || {};
|
|
1930
|
+
target.state.profiles[target.profileName] = {
|
|
1931
|
+
...target.bound,
|
|
1932
|
+
baseUrl: target.profile.baseUrl,
|
|
1933
|
+
appType: target.appType,
|
|
1934
|
+
updatedAt: new Date().toISOString(),
|
|
1935
|
+
};
|
|
1936
|
+
const nextBound = target.state.profiles[target.profileName];
|
|
1937
|
+
ensureResourceBuckets(nextBound);
|
|
1938
|
+
nextBound.resources.pages[pageCode] = {
|
|
1939
|
+
...(nextBound.resources.pages[pageCode] || {}),
|
|
1940
|
+
...extra,
|
|
1941
|
+
pageId,
|
|
1942
|
+
updatedAt: new Date().toISOString(),
|
|
1943
|
+
};
|
|
1944
|
+
saveProjectState(target.state);
|
|
1945
|
+
target.bound = nextBound;
|
|
1946
|
+
}
|
|
1947
|
+
|
|
1948
|
+
function saveMenuResource(target, menuCode, menuId, extra = {}) {
|
|
1949
|
+
target.state.profiles = target.state.profiles || {};
|
|
1950
|
+
target.state.profiles[target.profileName] = {
|
|
1951
|
+
...target.bound,
|
|
1952
|
+
baseUrl: target.profile.baseUrl,
|
|
1953
|
+
appType: target.appType,
|
|
1954
|
+
updatedAt: new Date().toISOString(),
|
|
1955
|
+
};
|
|
1956
|
+
const nextBound = target.state.profiles[target.profileName];
|
|
1957
|
+
ensureResourceBuckets(nextBound);
|
|
1958
|
+
nextBound.resources.menus[menuCode] = {
|
|
1959
|
+
...(nextBound.resources.menus[menuCode] || {}),
|
|
1960
|
+
...extra,
|
|
1961
|
+
menuId,
|
|
1962
|
+
updatedAt: new Date().toISOString(),
|
|
1963
|
+
};
|
|
1964
|
+
saveProjectState(target.state);
|
|
1965
|
+
target.bound = nextBound;
|
|
1966
|
+
}
|
|
1967
|
+
|
|
1968
|
+
function saveWorkflowResource(target, workflowCode, workflowId, extra = {}) {
|
|
1969
|
+
target.state.profiles = target.state.profiles || {};
|
|
1970
|
+
target.state.profiles[target.profileName] = {
|
|
1971
|
+
...target.bound,
|
|
1972
|
+
baseUrl: target.profile.baseUrl,
|
|
1973
|
+
appType: target.appType,
|
|
1974
|
+
updatedAt: new Date().toISOString(),
|
|
1975
|
+
};
|
|
1976
|
+
const nextBound = target.state.profiles[target.profileName];
|
|
1977
|
+
ensureResourceBuckets(nextBound);
|
|
1978
|
+
nextBound.resources.workflows[workflowCode] = {
|
|
1979
|
+
...(nextBound.resources.workflows[workflowCode] || {}),
|
|
1980
|
+
...extra,
|
|
1981
|
+
workflowId,
|
|
1982
|
+
updatedAt: new Date().toISOString(),
|
|
1983
|
+
};
|
|
1984
|
+
saveProjectState(target.state);
|
|
1985
|
+
target.bound = nextBound;
|
|
1986
|
+
}
|
|
1987
|
+
|
|
1988
|
+
function saveAutomationResource(target, automationCode, automationId, extra = {}) {
|
|
1989
|
+
target.state.profiles = target.state.profiles || {};
|
|
1990
|
+
target.state.profiles[target.profileName] = {
|
|
1991
|
+
...target.bound,
|
|
1992
|
+
baseUrl: target.profile.baseUrl,
|
|
1993
|
+
appType: target.appType,
|
|
1994
|
+
updatedAt: new Date().toISOString(),
|
|
1995
|
+
};
|
|
1996
|
+
const nextBound = target.state.profiles[target.profileName];
|
|
1997
|
+
ensureResourceBuckets(nextBound);
|
|
1998
|
+
nextBound.resources.automations[automationCode] = {
|
|
1999
|
+
...(nextBound.resources.automations[automationCode] || {}),
|
|
2000
|
+
...extra,
|
|
2001
|
+
automationId,
|
|
2002
|
+
updatedAt: new Date().toISOString(),
|
|
2003
|
+
};
|
|
2004
|
+
saveProjectState(target.state);
|
|
2005
|
+
target.bound = nextBound;
|
|
2006
|
+
}
|
|
2007
|
+
|
|
2008
|
+
function saveRoleResource(target, roleCode, roleId, extra = {}) {
|
|
2009
|
+
target.state.profiles = target.state.profiles || {};
|
|
2010
|
+
target.state.profiles[target.profileName] = {
|
|
2011
|
+
...target.bound,
|
|
2012
|
+
baseUrl: target.profile.baseUrl,
|
|
2013
|
+
appType: target.appType,
|
|
2014
|
+
updatedAt: new Date().toISOString(),
|
|
2015
|
+
};
|
|
2016
|
+
const nextBound = target.state.profiles[target.profileName];
|
|
2017
|
+
ensureResourceBuckets(nextBound);
|
|
2018
|
+
nextBound.resources.roles[roleCode] = {
|
|
2019
|
+
...(nextBound.resources.roles[roleCode] || {}),
|
|
2020
|
+
...extra,
|
|
2021
|
+
roleId,
|
|
2022
|
+
updatedAt: new Date().toISOString(),
|
|
2023
|
+
};
|
|
2024
|
+
saveProjectState(target.state);
|
|
2025
|
+
target.bound = nextBound;
|
|
2026
|
+
}
|
|
2027
|
+
|
|
2028
|
+
function savePagePermissionGroupResource(target, groupCode, groupId, extra = {}) {
|
|
2029
|
+
target.state.profiles = target.state.profiles || {};
|
|
2030
|
+
target.state.profiles[target.profileName] = {
|
|
2031
|
+
...target.bound,
|
|
2032
|
+
baseUrl: target.profile.baseUrl,
|
|
2033
|
+
appType: target.appType,
|
|
2034
|
+
updatedAt: new Date().toISOString(),
|
|
2035
|
+
};
|
|
2036
|
+
const nextBound = target.state.profiles[target.profileName];
|
|
2037
|
+
ensureResourceBuckets(nextBound);
|
|
2038
|
+
nextBound.resources.pagePermissionGroups[groupCode] = {
|
|
2039
|
+
...(nextBound.resources.pagePermissionGroups[groupCode] || {}),
|
|
2040
|
+
...extra,
|
|
2041
|
+
groupId,
|
|
2042
|
+
updatedAt: new Date().toISOString(),
|
|
2043
|
+
};
|
|
2044
|
+
saveProjectState(target.state);
|
|
2045
|
+
target.bound = nextBound;
|
|
2046
|
+
}
|
|
2047
|
+
|
|
2048
|
+
function saveFormPermissionGroupResource(target, groupCode, groupId, extra = {}) {
|
|
2049
|
+
target.state.profiles = target.state.profiles || {};
|
|
2050
|
+
target.state.profiles[target.profileName] = {
|
|
2051
|
+
...target.bound,
|
|
2052
|
+
baseUrl: target.profile.baseUrl,
|
|
2053
|
+
appType: target.appType,
|
|
2054
|
+
updatedAt: new Date().toISOString(),
|
|
2055
|
+
};
|
|
2056
|
+
const nextBound = target.state.profiles[target.profileName];
|
|
2057
|
+
ensureResourceBuckets(nextBound);
|
|
2058
|
+
nextBound.resources.formPermissionGroups[groupCode] = {
|
|
2059
|
+
...(nextBound.resources.formPermissionGroups[groupCode] || {}),
|
|
2060
|
+
...extra,
|
|
2061
|
+
groupId,
|
|
2062
|
+
updatedAt: new Date().toISOString(),
|
|
2063
|
+
};
|
|
2064
|
+
saveProjectState(target.state);
|
|
2065
|
+
target.bound = nextBound;
|
|
2066
|
+
}
|
|
2067
|
+
|
|
2068
|
+
function readJsonArg(value, label) {
|
|
2069
|
+
return readJsonArgWithBase(value, label).data;
|
|
2070
|
+
}
|
|
2071
|
+
|
|
2072
|
+
function readJsonArgWithBase(value, label) {
|
|
2073
|
+
if (!value) fail(`缺少 ${label}`);
|
|
2074
|
+
const raw = String(value).trim();
|
|
2075
|
+
try {
|
|
2076
|
+
if (raw.startsWith('{') || raw.startsWith('[')) {
|
|
2077
|
+
return { data: JSON.parse(raw), baseDir: process.cwd() };
|
|
2078
|
+
}
|
|
2079
|
+
const filePath = path.resolve(process.cwd(), raw);
|
|
2080
|
+
return {
|
|
2081
|
+
data: JSON.parse(fs.readFileSync(filePath, 'utf8')),
|
|
2082
|
+
baseDir: path.dirname(filePath),
|
|
2083
|
+
};
|
|
2084
|
+
} catch (error) {
|
|
2085
|
+
fail(`无法读取 ${label}: ${error.message}`);
|
|
2086
|
+
}
|
|
2087
|
+
}
|
|
2088
|
+
|
|
2089
|
+
async function readDefinitionJsonArg(config, profileName, value, label) {
|
|
2090
|
+
const { data, baseDir } = readJsonArgWithBase(value, label);
|
|
2091
|
+
const uploadCache = new Map();
|
|
2092
|
+
await resolveJsCodeSnapshotFiles(config, profileName, data, baseDir, uploadCache);
|
|
2093
|
+
return data;
|
|
2094
|
+
}
|
|
2095
|
+
|
|
2096
|
+
async function resolveJsCodeSnapshotFiles(config, profileName, value, baseDir, uploadCache) {
|
|
2097
|
+
if (!value || typeof value !== 'object') return;
|
|
2098
|
+
if (Array.isArray(value)) {
|
|
2099
|
+
for (const item of value) {
|
|
2100
|
+
await resolveJsCodeSnapshotFiles(config, profileName, item, baseDir, uploadCache);
|
|
2101
|
+
}
|
|
2102
|
+
return;
|
|
2103
|
+
}
|
|
2104
|
+
|
|
2105
|
+
if (
|
|
2106
|
+
value.sourceFile &&
|
|
2107
|
+
typeof value.sourceFile === 'object' &&
|
|
2108
|
+
value.sourceFile.localPath
|
|
2109
|
+
) {
|
|
2110
|
+
const sourceFile = await uploadJsCodeSnapshotFile(
|
|
2111
|
+
config,
|
|
2112
|
+
profileName,
|
|
2113
|
+
value.sourceFile.localPath,
|
|
2114
|
+
baseDir,
|
|
2115
|
+
value.scriptCode,
|
|
2116
|
+
uploadCache
|
|
2117
|
+
);
|
|
2118
|
+
value.runtimeMode = value.runtimeMode || 'trusted_node';
|
|
2119
|
+
value.sourceType = 'file_snapshot';
|
|
2120
|
+
value.sourceFile = sourceFile;
|
|
2121
|
+
value.code = value.code || '';
|
|
2122
|
+
}
|
|
2123
|
+
|
|
2124
|
+
for (const child of Object.values(value)) {
|
|
2125
|
+
await resolveJsCodeSnapshotFiles(config, profileName, child, baseDir, uploadCache);
|
|
2126
|
+
}
|
|
2127
|
+
}
|
|
2128
|
+
|
|
2129
|
+
async function uploadJsCodeSnapshotFile(
|
|
2130
|
+
config,
|
|
2131
|
+
profileName,
|
|
2132
|
+
localPath,
|
|
2133
|
+
baseDir,
|
|
2134
|
+
scriptCode,
|
|
2135
|
+
uploadCache
|
|
2136
|
+
) {
|
|
2137
|
+
const rawLocalPath = String(localPath);
|
|
2138
|
+
const baseResolvedPath = path.resolve(baseDir || process.cwd(), rawLocalPath);
|
|
2139
|
+
const cwdResolvedPath = path.resolve(process.cwd(), rawLocalPath);
|
|
2140
|
+
const resolvedLocalPath = fs.existsSync(baseResolvedPath)
|
|
2141
|
+
? baseResolvedPath
|
|
2142
|
+
: cwdResolvedPath;
|
|
2143
|
+
const bundlePath = resolveJsCodeBundlePath(resolvedLocalPath, scriptCode);
|
|
2144
|
+
if (!fs.existsSync(bundlePath)) {
|
|
2145
|
+
fail(`JS_CODE bundle不存在: ${bundlePath}`);
|
|
2146
|
+
}
|
|
2147
|
+
const extension = path.extname(bundlePath).toLowerCase();
|
|
2148
|
+
if (!['.js', '.cjs', '.mjs'].includes(extension)) {
|
|
2149
|
+
fail(`JS_CODE bundle必须是 .js/.cjs/.mjs: ${bundlePath}`);
|
|
2150
|
+
}
|
|
2151
|
+
const cacheKey = bundlePath;
|
|
2152
|
+
if (uploadCache.has(cacheKey)) return uploadCache.get(cacheKey);
|
|
2153
|
+
|
|
2154
|
+
const buffer = fs.readFileSync(bundlePath);
|
|
2155
|
+
const uploaded = await requestFormWithAuth(
|
|
2156
|
+
config,
|
|
2157
|
+
profileName,
|
|
2158
|
+
'/file/js-code-snapshot/upload',
|
|
2159
|
+
() => {
|
|
2160
|
+
const formData = new FormData();
|
|
2161
|
+
const blob = new Blob([buffer], { type: 'application/javascript' });
|
|
2162
|
+
formData.append('file', blob, path.basename(bundlePath));
|
|
2163
|
+
return formData;
|
|
2164
|
+
}
|
|
2165
|
+
);
|
|
2166
|
+
const sourceFile = {
|
|
2167
|
+
bucketName: uploaded.bucketName,
|
|
2168
|
+
objectName: uploaded.objectName,
|
|
2169
|
+
sha256: uploaded.sha256,
|
|
2170
|
+
size: uploaded.size,
|
|
2171
|
+
originalName: uploaded.originalName,
|
|
2172
|
+
contentType: uploaded.contentType,
|
|
2173
|
+
};
|
|
2174
|
+
uploadCache.set(cacheKey, sourceFile);
|
|
2175
|
+
print(`已上传JS_CODE快照: ${path.relative(process.cwd(), bundlePath)} (${sourceFile.sha256})`);
|
|
2176
|
+
return sourceFile;
|
|
2177
|
+
}
|
|
2178
|
+
|
|
2179
|
+
function resolveJsCodeBundlePath(localPath, scriptCode) {
|
|
2180
|
+
const extension = path.extname(localPath).toLowerCase();
|
|
2181
|
+
if (['.js', '.cjs', '.mjs'].includes(extension)) {
|
|
2182
|
+
fail(
|
|
2183
|
+
`JS_CODE V2 要求 AI 源码使用 TypeScript,请把 sourceFile.localPath 指向 src/js-code-nodes/<scriptCode>/index.ts,而不是已构建的 ${extension} 文件: ${localPath}`
|
|
2184
|
+
);
|
|
2185
|
+
}
|
|
2186
|
+
if (extension !== '.ts') {
|
|
2187
|
+
fail(
|
|
2188
|
+
`JS_CODE V2 sourceFile.localPath 必须指向 TypeScript 源文件 src/js-code-nodes/<scriptCode>/index.ts: ${localPath}`
|
|
2189
|
+
);
|
|
2190
|
+
}
|
|
2191
|
+
|
|
2192
|
+
const normalized = localPath.replace(/\\/g, '/');
|
|
2193
|
+
const matched = normalized.match(/src\/js-code-nodes\/([^/]+)\/index\.tsx?$/);
|
|
2194
|
+
const resolvedScriptCode = scriptCode || matched?.[1];
|
|
2195
|
+
if (!resolvedScriptCode) {
|
|
2196
|
+
fail(
|
|
2197
|
+
`无法从 sourceFile.localPath 推断 scriptCode,请使用 src/js-code-nodes/<scriptCode>/index.ts: ${localPath}`
|
|
2198
|
+
);
|
|
2199
|
+
}
|
|
2200
|
+
|
|
2201
|
+
const workspaceRoot = findWorkspaceRoot(localPath);
|
|
2202
|
+
const result = spawnSync('pnpm', ['build-js-code', '--script', resolvedScriptCode], {
|
|
2203
|
+
cwd: workspaceRoot,
|
|
2204
|
+
stdio: 'inherit',
|
|
2205
|
+
});
|
|
2206
|
+
if (result.status !== 0) {
|
|
2207
|
+
fail(`JS_CODE bundle构建失败: ${resolvedScriptCode}`);
|
|
2208
|
+
}
|
|
2209
|
+
return path.join(workspaceRoot, 'dist', 'js-code-nodes', resolvedScriptCode, 'index.cjs');
|
|
2210
|
+
}
|
|
2211
|
+
|
|
2212
|
+
function findWorkspaceRoot(startPath) {
|
|
2213
|
+
if (!fs.existsSync(startPath)) {
|
|
2214
|
+
return process.cwd();
|
|
2215
|
+
}
|
|
2216
|
+
let dir = fs.statSync(startPath).isDirectory() ? startPath : path.dirname(startPath);
|
|
2217
|
+
while (dir && dir !== path.dirname(dir)) {
|
|
2218
|
+
const packageFile = path.join(dir, 'package.json');
|
|
2219
|
+
if (fs.existsSync(packageFile)) {
|
|
2220
|
+
try {
|
|
2221
|
+
const pkg = JSON.parse(fs.readFileSync(packageFile, 'utf8'));
|
|
2222
|
+
if (pkg.scripts?.['build-js-code'] || pkg.name === 'sy-lowcode-app-workspace') {
|
|
2223
|
+
return dir;
|
|
2224
|
+
}
|
|
2225
|
+
} catch {
|
|
2226
|
+
return dir;
|
|
2227
|
+
}
|
|
2228
|
+
}
|
|
2229
|
+
dir = path.dirname(dir);
|
|
2230
|
+
}
|
|
2231
|
+
return process.cwd();
|
|
2232
|
+
}
|
|
2233
|
+
|
|
2234
|
+
function apiPathWithQuery(apiPath, query) {
|
|
2235
|
+
const params = new URLSearchParams();
|
|
2236
|
+
for (const [key, value] of Object.entries(query || {})) {
|
|
2237
|
+
if (value === undefined || value === null || value === false || value === '') continue;
|
|
2238
|
+
params.set(key, String(value));
|
|
2239
|
+
}
|
|
2240
|
+
const queryString = params.toString();
|
|
2241
|
+
return queryString ? `${apiPath}?${queryString}` : apiPath;
|
|
2242
|
+
}
|
|
2243
|
+
|
|
2244
|
+
function splitList(value) {
|
|
2245
|
+
if (!value) return [];
|
|
2246
|
+
return String(value)
|
|
2247
|
+
.split(',')
|
|
2248
|
+
.map(item => item.trim())
|
|
2249
|
+
.filter(Boolean);
|
|
2250
|
+
}
|
|
2251
|
+
|
|
2252
|
+
function renderTerminalQr(text) {
|
|
2253
|
+
try {
|
|
2254
|
+
require('qrcode-terminal').generate(text, { small: true });
|
|
2255
|
+
} catch {
|
|
2256
|
+
print('未安装 qrcode-terminal,已改为展示登录链接。');
|
|
2257
|
+
}
|
|
2258
|
+
}
|
|
2259
|
+
|
|
2260
|
+
function unwrapApi(payload) {
|
|
2261
|
+
if (!payload || typeof payload !== 'object') return payload;
|
|
2262
|
+
if ('code' in payload && Number(payload.code) >= 400) {
|
|
2263
|
+
const error = new Error(payload.message || 'OpenXiangda API request failed');
|
|
2264
|
+
error.apiCode = Number(payload.code);
|
|
2265
|
+
throw error;
|
|
2266
|
+
}
|
|
2267
|
+
return 'data' in payload ? payload.data : payload;
|
|
2268
|
+
}
|
|
2269
|
+
|
|
2270
|
+
function isUnauthorized(error) {
|
|
2271
|
+
return (
|
|
2272
|
+
Number(error?.apiCode) === 401 ||
|
|
2273
|
+
String(error?.message || '').includes('HTTP 401')
|
|
2274
|
+
);
|
|
2275
|
+
}
|
|
2276
|
+
|
|
2277
|
+
async function requestWithAuth(config, profileName, apiPath, options = {}) {
|
|
2278
|
+
const resolved = getProfile(config, profileName);
|
|
2279
|
+
const profile = resolved.profile;
|
|
2280
|
+
if (!profile.token?.accessToken) {
|
|
2281
|
+
fail(`profile ${resolved.profileName} 未登录,请先执行 openxiangda login --profile ${resolved.profileName}`);
|
|
2282
|
+
}
|
|
2283
|
+
|
|
2284
|
+
try {
|
|
2285
|
+
const payload = await requestJson(profile.baseUrl, apiPath, {
|
|
2286
|
+
...options,
|
|
2287
|
+
accessToken: profile.token.accessToken,
|
|
2288
|
+
});
|
|
2289
|
+
return unwrapApi(payload);
|
|
2290
|
+
} catch (error) {
|
|
2291
|
+
if (!isUnauthorized(error) || !profile.token?.refreshToken) {
|
|
2292
|
+
throw error;
|
|
2293
|
+
}
|
|
2294
|
+
await refreshProfile(config, resolved.profileName);
|
|
2295
|
+
const payload = await requestJson(profile.baseUrl, apiPath, {
|
|
2296
|
+
...options,
|
|
2297
|
+
accessToken: profile.token.accessToken,
|
|
2298
|
+
});
|
|
2299
|
+
return unwrapApi(payload);
|
|
2300
|
+
}
|
|
2301
|
+
}
|
|
2302
|
+
|
|
2303
|
+
async function requestFormWithAuth(config, profileName, apiPath, formDataFactory) {
|
|
2304
|
+
const resolved = getProfile(config, profileName);
|
|
2305
|
+
const profile = resolved.profile;
|
|
2306
|
+
if (!profile.token?.accessToken) {
|
|
2307
|
+
fail(`profile ${resolved.profileName} 未登录,请先执行 openxiangda login --profile ${resolved.profileName}`);
|
|
2308
|
+
}
|
|
2309
|
+
|
|
2310
|
+
try {
|
|
2311
|
+
const payload = await requestForm(
|
|
2312
|
+
profile.baseUrl,
|
|
2313
|
+
apiPath,
|
|
2314
|
+
formDataFactory(),
|
|
2315
|
+
profile.token.accessToken
|
|
2316
|
+
);
|
|
2317
|
+
return unwrapApi(payload);
|
|
2318
|
+
} catch (error) {
|
|
2319
|
+
if (!isUnauthorized(error) || !profile.token?.refreshToken) {
|
|
2320
|
+
throw error;
|
|
2321
|
+
}
|
|
2322
|
+
await refreshProfile(config, resolved.profileName);
|
|
2323
|
+
const payload = await requestForm(
|
|
2324
|
+
profile.baseUrl,
|
|
2325
|
+
apiPath,
|
|
2326
|
+
formDataFactory(),
|
|
2327
|
+
profile.token.accessToken
|
|
2328
|
+
);
|
|
2329
|
+
return unwrapApi(payload);
|
|
2330
|
+
}
|
|
2331
|
+
}
|
|
2332
|
+
|
|
2333
|
+
async function requestForm(baseUrl, apiPath, formData, accessToken) {
|
|
2334
|
+
if (typeof fetch !== 'function') {
|
|
2335
|
+
throw new Error('当前 Node.js 版本不支持 fetch,请使用 Node.js 18 或更高版本');
|
|
2336
|
+
}
|
|
2337
|
+
const url = `${baseUrl.replace(/\/+$/, '')}${apiPath}`;
|
|
2338
|
+
const response = await fetch(url, {
|
|
2339
|
+
method: 'POST',
|
|
2340
|
+
headers: {
|
|
2341
|
+
accept: 'application/json',
|
|
2342
|
+
authorization: `Bearer ${accessToken}`,
|
|
2343
|
+
},
|
|
2344
|
+
body: formData,
|
|
2345
|
+
});
|
|
2346
|
+
const text = await response.text();
|
|
2347
|
+
let payload = null;
|
|
2348
|
+
if (text) {
|
|
2349
|
+
try {
|
|
2350
|
+
payload = JSON.parse(text);
|
|
2351
|
+
} catch {
|
|
2352
|
+
payload = { message: text };
|
|
2353
|
+
}
|
|
2354
|
+
}
|
|
2355
|
+
if (!response.ok) {
|
|
2356
|
+
const message = payload?.message || response.statusText || 'request failed';
|
|
2357
|
+
throw new Error(`HTTP ${response.status}: ${message}`);
|
|
2358
|
+
}
|
|
2359
|
+
return payload;
|
|
2360
|
+
}
|
|
2361
|
+
|
|
2362
|
+
async function refreshProfile(config, profileName) {
|
|
2363
|
+
const { profile } = getProfile(config, profileName);
|
|
2364
|
+
if (!profile.token?.refreshToken) {
|
|
2365
|
+
fail(`profile ${profileName} 没有 refresh token,请重新登录`);
|
|
2366
|
+
}
|
|
2367
|
+
const payload = await requestJson(profile.baseUrl, '/openxiangda-api/v1/auth/refresh', {
|
|
2368
|
+
method: 'POST',
|
|
2369
|
+
body: { refreshToken: profile.token.refreshToken },
|
|
2370
|
+
});
|
|
2371
|
+
const data = unwrapApi(payload);
|
|
2372
|
+
profile.token = {
|
|
2373
|
+
...profile.token,
|
|
2374
|
+
accessToken: data.accessToken,
|
|
2375
|
+
refreshToken: data.refreshToken,
|
|
2376
|
+
accessTokenExpiresAt: data.accessTokenExpiresAt,
|
|
2377
|
+
refreshTokenExpiresAt: data.refreshTokenExpiresAt,
|
|
2378
|
+
};
|
|
2379
|
+
profile.updatedAt = new Date().toISOString();
|
|
2380
|
+
return {
|
|
2381
|
+
accessTokenExpiresAt: profile.token.accessTokenExpiresAt,
|
|
2382
|
+
refreshTokenExpiresAt: profile.token.refreshTokenExpiresAt,
|
|
2383
|
+
};
|
|
2384
|
+
}
|
|
2385
|
+
|
|
2386
|
+
function runWorkspacePublish(profileName, profile, appType) {
|
|
2387
|
+
const packageFile = path.join(process.cwd(), 'package.json');
|
|
2388
|
+
if (!fs.existsSync(packageFile)) {
|
|
2389
|
+
fail('当前目录没有 package.json,无法识别工作区发布脚本');
|
|
2390
|
+
}
|
|
2391
|
+
const pkg = JSON.parse(fs.readFileSync(packageFile, 'utf8'));
|
|
2392
|
+
const scripts = pkg.scripts || {};
|
|
2393
|
+
const scriptName = scripts['openxiangda:publish']
|
|
2394
|
+
? 'openxiangda:publish'
|
|
2395
|
+
: scripts['publish:all']
|
|
2396
|
+
? 'publish:all'
|
|
2397
|
+
: null;
|
|
2398
|
+
if (!scriptName) {
|
|
2399
|
+
fail('当前工作区缺少 openxiangda:publish 或 publish:all 脚本');
|
|
2400
|
+
}
|
|
2401
|
+
|
|
2402
|
+
const usePnpm = fs.existsSync(path.join(process.cwd(), 'pnpm-lock.yaml'));
|
|
2403
|
+
const command = usePnpm ? 'pnpm' : 'npm';
|
|
2404
|
+
const args = usePnpm ? ['run', scriptName] : ['run', scriptName];
|
|
2405
|
+
const result = spawnSync(command, args, {
|
|
2406
|
+
cwd: process.cwd(),
|
|
2407
|
+
stdio: 'inherit',
|
|
2408
|
+
env: {
|
|
2409
|
+
...process.env,
|
|
2410
|
+
OPENXIANGDA_PROFILE: profileName,
|
|
2411
|
+
OPENXIANGDA_BASE_URL: profile.baseUrl,
|
|
2412
|
+
OPENXIANGDA_ACCESS_TOKEN: profile.token.accessToken,
|
|
2413
|
+
OPENXIANGDA_APP_TYPE: appType,
|
|
2414
|
+
},
|
|
2415
|
+
});
|
|
2416
|
+
if (result.status !== 0) {
|
|
2417
|
+
process.exit(result.status || 1);
|
|
2418
|
+
}
|
|
2419
|
+
}
|
|
2420
|
+
|
|
2421
|
+
module.exports = {
|
|
2422
|
+
main,
|
|
2423
|
+
};
|