@josephyan/qingflow-app-builder-mcp 1.1.24 → 1.1.26
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 +5 -6
- package/docs/local-agent-install.md +6 -57
- package/entry_point.py +1 -1
- package/npm/bin/qingflow-app-builder-mcp.mjs +33 -2
- package/npm/lib/runtime.mjs +101 -21
- package/npm/scripts/postinstall.mjs +10 -1
- package/package.json +2 -3
- package/pyproject.toml +1 -1
- package/skills/qingflow-app-builder/SKILL.md +42 -104
- package/skills/qingflow-app-builder/references/build-complete-system.md +428 -0
- package/skills/qingflow-app-builder/references/build-single-app.md +530 -0
- package/skills/qingflow-app-builder/references/create-app.md +11 -30
- package/skills/qingflow-app-builder/references/environments.md +1 -1
- package/skills/qingflow-app-builder/references/gotchas.md +9 -31
- package/skills/qingflow-app-builder/references/match-rules.md +0 -15
- package/skills/qingflow-app-builder/references/solution-playbooks.md +0 -10
- package/skills/qingflow-app-builder/references/tool-selection.md +23 -38
- package/skills/qingflow-app-builder/references/update-flow.md +38 -22
- package/skills/qingflow-app-builder/references/update-schema.md +0 -14
- package/skills/qingflow-app-builder/references/update-views.md +14 -83
- package/skills/qingflow-app-builder-code-integrations/SKILL.md +3 -5
- package/skills/qingflow-app-builder-code-integrations/references/code-block.md +1 -1
- package/skills/qingflow-app-builder-code-integrations/references/q-linker.md +1 -1
- package/skills/qingflow-mcp-setup/SKILL.md +111 -0
- package/skills/qingflow-mcp-setup/agents/openai.yaml +4 -0
- package/skills/qingflow-mcp-setup/references/claude-desktop.md +34 -0
- package/skills/qingflow-mcp-setup/references/environments.md +61 -0
- package/skills/qingflow-mcp-setup/references/generic-stdio.md +31 -0
- package/skills/qingflow-mcp-setup/scripts/check_local_server.sh +38 -0
- package/skills/qingflow-workflow-builder/SKILL.md +96 -0
- package/skills/qingflow-workflow-builder/manifest.yaml +8 -0
- package/skills/qingflow-workflow-builder/references/01-overview.md +45 -0
- package/skills/qingflow-workflow-builder/references/02-update-mode.md +53 -0
- package/skills/qingflow-workflow-builder/references/03-flow-patterns.md +57 -0
- package/skills/qingflow-workflow-builder/references/04-stage1-business-modeling.md +131 -0
- package/skills/qingflow-workflow-builder/references/05-stage2-members-roles.md +29 -0
- package/skills/qingflow-workflow-builder/references/06-stage3-build-spec.md +165 -0
- package/skills/qingflow-workflow-builder/references/07-stage4-validate-spec.md +33 -0
- package/skills/qingflow-workflow-builder/references/08-stage5-apply-verify.md +51 -0
- package/skills/qingflow-workflow-builder/references/09-stage6-summary.md +88 -0
- package/skills/qingflow-workflow-builder/references/10-node-config-reference.md +93 -0
- package/skills/qingflow-workflow-builder/references/11-troubleshooting.md +15 -0
- package/skills/qingflow-workflow-builder/scripts/diff_flow_spec.py +275 -0
- package/skills/qingflow-workflow-builder/scripts/validate_flow_spec.py +605 -0
- package/src/qingflow_mcp/__init__.py +1 -1
- package/src/qingflow_mcp/__main__.py +2 -6
- package/src/qingflow_mcp/builder_facade/models.py +108 -485
- package/src/qingflow_mcp/builder_facade/service.py +1686 -6674
- package/src/qingflow_mcp/cli/commands/builder.py +364 -322
- package/src/qingflow_mcp/cli/commands/chart.py +1 -1
- package/src/qingflow_mcp/cli/commands/common.py +3 -12
- package/src/qingflow_mcp/cli/commands/exports.py +2 -2
- package/src/qingflow_mcp/cli/commands/imports.py +3 -3
- package/src/qingflow_mcp/cli/commands/portal.py +2 -2
- package/src/qingflow_mcp/cli/commands/record.py +27 -101
- package/src/qingflow_mcp/cli/commands/task.py +47 -28
- package/src/qingflow_mcp/cli/commands/view.py +1 -1
- package/src/qingflow_mcp/cli/context.py +3 -0
- package/src/qingflow_mcp/cli/formatters.py +16 -784
- package/src/qingflow_mcp/cli/main.py +33 -117
- package/src/qingflow_mcp/errors.py +2 -43
- package/src/qingflow_mcp/public_surface.py +17 -26
- package/src/qingflow_mcp/response_trim.py +17 -81
- package/src/qingflow_mcp/server.py +12 -14
- package/src/qingflow_mcp/server_app_builder.py +36 -79
- package/src/qingflow_mcp/server_app_user.py +16 -22
- package/src/qingflow_mcp/session_store.py +7 -11
- package/src/qingflow_mcp/solution/compiler/__init__.py +1 -3
- package/src/qingflow_mcp/solution/executor.py +18 -245
- package/src/qingflow_mcp/tools/ai_builder_tools.py +1396 -3100
- package/src/qingflow_mcp/tools/app_tools.py +43 -184
- package/src/qingflow_mcp/tools/approval_tools.py +35 -197
- package/src/qingflow_mcp/tools/auth_tools.py +16 -92
- package/src/qingflow_mcp/tools/code_block_tools.py +40 -298
- package/src/qingflow_mcp/tools/custom_button_tools.py +10 -64
- package/src/qingflow_mcp/tools/directory_tools.py +72 -236
- package/src/qingflow_mcp/tools/export_tools.py +34 -244
- package/src/qingflow_mcp/tools/feedback_tools.py +0 -9
- package/src/qingflow_mcp/tools/file_tools.py +3 -9
- package/src/qingflow_mcp/tools/import_tools.py +49 -336
- package/src/qingflow_mcp/tools/navigation_tools.py +12 -91
- package/src/qingflow_mcp/tools/package_tools.py +6 -118
- package/src/qingflow_mcp/tools/portal_tools.py +3 -39
- package/src/qingflow_mcp/tools/qingbi_report_tools.py +7 -116
- package/src/qingflow_mcp/tools/record_tools.py +360 -1145
- package/src/qingflow_mcp/tools/resource_read_tools.py +39 -188
- package/src/qingflow_mcp/tools/role_tools.py +9 -80
- package/src/qingflow_mcp/tools/solution_tools.py +45 -59
- package/src/qingflow_mcp/tools/task_context_tools.py +158 -662
- package/src/qingflow_mcp/tools/task_tools.py +29 -113
- package/src/qingflow_mcp/tools/view_tools.py +3 -106
- package/src/qingflow_mcp/tools/workflow_tools.py +4 -48
- package/src/qingflow_mcp/tools/workspace_tools.py +3 -71
- package/npm/bin/qingflow-skills.mjs +0 -5
- package/skills/qingflow-app-builder/references/complete-system-development-guide.md +0 -172
- package/skills/qingflow-app-builder/references/single-app-development-guide.md +0 -87
- package/skills/qingflow-app-builder/scripts/validate_system_build_summary.py +0 -124
- package/src/qingflow_mcp/solution/compiler/workflow_compiler.py +0 -173
|
@@ -1,11 +1,94 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import argparse
|
|
4
|
-
from
|
|
4
|
+
from typing import Any
|
|
5
5
|
|
|
6
6
|
from ..context import CliContext
|
|
7
7
|
from ..json_io import load_json_value
|
|
8
|
-
from .common import load_list_arg, load_object_arg,
|
|
8
|
+
from .common import load_list_arg, load_object_arg, raise_config_error, require_list_arg
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def _parse_app_keys_arg(raw: Any) -> list[str] | None:
|
|
12
|
+
"""Parse comma-separated --app-keys argument into a list, or return None if empty."""
|
|
13
|
+
if not raw:
|
|
14
|
+
return None
|
|
15
|
+
keys = [k.strip() for k in str(raw).split(",") if k.strip()]
|
|
16
|
+
return keys if keys else None
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _load_schema_apps_payload(path: str | None, *, cli_package_id: int | None) -> tuple[list[Any], int | None]:
|
|
20
|
+
"""Load schema --apps-file.
|
|
21
|
+
|
|
22
|
+
Public docs recommend {"package_id": 123, "apps": [...]}; older payloads used
|
|
23
|
+
a raw apps array, and some agents accidentally wrapped the object in a
|
|
24
|
+
singleton array. Keep all three shapes on the same multi-app path.
|
|
25
|
+
"""
|
|
26
|
+
if not path:
|
|
27
|
+
return [], cli_package_id
|
|
28
|
+
payload = load_json_value(path, option_name="--apps-file")
|
|
29
|
+
package_id = cli_package_id
|
|
30
|
+
if isinstance(payload, dict):
|
|
31
|
+
if package_id is None:
|
|
32
|
+
raw_package_id = payload.get("package_id")
|
|
33
|
+
if isinstance(raw_package_id, int):
|
|
34
|
+
package_id = raw_package_id
|
|
35
|
+
elif raw_package_id is not None and str(raw_package_id).strip().isdigit():
|
|
36
|
+
package_id = int(str(raw_package_id).strip())
|
|
37
|
+
apps = payload.get("apps")
|
|
38
|
+
elif isinstance(payload, list):
|
|
39
|
+
if len(payload) == 1 and isinstance(payload[0], dict) and "apps" in payload[0]:
|
|
40
|
+
wrapper = payload[0]
|
|
41
|
+
if package_id is None:
|
|
42
|
+
raw_package_id = wrapper.get("package_id")
|
|
43
|
+
if isinstance(raw_package_id, int):
|
|
44
|
+
package_id = raw_package_id
|
|
45
|
+
elif raw_package_id is not None and str(raw_package_id).strip().isdigit():
|
|
46
|
+
package_id = int(str(raw_package_id).strip())
|
|
47
|
+
apps = wrapper.get("apps")
|
|
48
|
+
elif any(isinstance(item, dict) and "apps" in item for item in payload):
|
|
49
|
+
raise_config_error(
|
|
50
|
+
"APPS_FILE_SHAPE_INVALID: --apps-file may be {package_id, apps}, a raw apps array, or a singleton wrapper array only.",
|
|
51
|
+
fix_hint='Use {"package_id": 123, "apps": [{"client_key": "main", "app_name": "主表", "icon": "database", "color": "blue", "add_fields": []}]}',
|
|
52
|
+
)
|
|
53
|
+
else:
|
|
54
|
+
apps = payload
|
|
55
|
+
else:
|
|
56
|
+
raise_config_error(
|
|
57
|
+
"APPS_FILE_SHAPE_INVALID: --apps-file must be {package_id, apps} or a JSON array of app items.",
|
|
58
|
+
fix_hint='Use {"package_id": 123, "apps": [{"client_key": "main", "app_name": "主表", "icon": "database", "color": "blue", "add_fields": []}]}',
|
|
59
|
+
)
|
|
60
|
+
if not isinstance(apps, list):
|
|
61
|
+
raise_config_error(
|
|
62
|
+
"APPS_FILE_SHAPE_INVALID: --apps-file apps must be a JSON array.",
|
|
63
|
+
fix_hint='Use {"package_id": 123, "apps": [{"client_key": "main", "app_name": "主表", "icon": "database", "color": "blue", "add_fields": []}]}',
|
|
64
|
+
)
|
|
65
|
+
return apps, package_id
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _load_views_payload(path: str | None) -> list[Any]:
|
|
69
|
+
"""Load views --views-file.
|
|
70
|
+
|
|
71
|
+
Public docs recommend {"views": [...]}; accept a raw array as a small
|
|
72
|
+
compatibility convenience because most other builder file flags are arrays.
|
|
73
|
+
"""
|
|
74
|
+
if not path:
|
|
75
|
+
return []
|
|
76
|
+
payload = load_json_value(path, option_name="--views-file")
|
|
77
|
+
if isinstance(payload, dict):
|
|
78
|
+
views = payload.get("views")
|
|
79
|
+
elif isinstance(payload, list):
|
|
80
|
+
views = payload
|
|
81
|
+
else:
|
|
82
|
+
raise_config_error(
|
|
83
|
+
"VIEWS_FILE_SHAPE_INVALID: --views-file must be {views: [...]} or a JSON array.",
|
|
84
|
+
fix_hint='Use {"views": [{"operation": "upsert", "app_key": "APP_KEY", "name": "业务视图", "type": "table", "columns": ["字段A"]}]}',
|
|
85
|
+
)
|
|
86
|
+
if not isinstance(views, list):
|
|
87
|
+
raise_config_error(
|
|
88
|
+
"VIEWS_FILE_SHAPE_INVALID: --views-file views must be a JSON array.",
|
|
89
|
+
fix_hint='Use {"views": [{"operation": "upsert", "app_key": "APP_KEY", "name": "业务视图", "type": "table", "columns": ["字段A"]}]}',
|
|
90
|
+
)
|
|
91
|
+
return views
|
|
9
92
|
|
|
10
93
|
|
|
11
94
|
def register(subparsers: argparse._SubParsersAction[argparse.ArgumentParser]) -> None:
|
|
@@ -22,7 +105,7 @@ def register(subparsers: argparse._SubParsersAction[argparse.ArgumentParser]) ->
|
|
|
22
105
|
file_upload_local.add_argument("--bucket-type")
|
|
23
106
|
file_upload_local.add_argument("--path-id", type=int)
|
|
24
107
|
file_upload_local.add_argument("--file-related-url")
|
|
25
|
-
file_upload_local.set_defaults(handler=_handle_file_upload_local, format_hint="
|
|
108
|
+
file_upload_local.set_defaults(handler=_handle_file_upload_local, format_hint="generic")
|
|
26
109
|
|
|
27
110
|
feedback = builder_subparsers.add_parser("feedback", help="builder 侧反馈提交")
|
|
28
111
|
feedback_subparsers = feedback.add_subparsers(dest="builder_feedback_command", required=True)
|
|
@@ -38,7 +121,7 @@ def register(subparsers: argparse._SubParsersAction[argparse.ArgumentParser]) ->
|
|
|
38
121
|
feedback_submit.add_argument("--record-id")
|
|
39
122
|
feedback_submit.add_argument("--workflow-node-id")
|
|
40
123
|
feedback_submit.add_argument("--note")
|
|
41
|
-
feedback_submit.set_defaults(handler=_handle_feedback_submit, format_hint="
|
|
124
|
+
feedback_submit.set_defaults(handler=_handle_feedback_submit, format_hint="generic")
|
|
42
125
|
|
|
43
126
|
contract = builder_subparsers.add_parser("contract", help="读取 builder tool 合约")
|
|
44
127
|
contract.add_argument("--tool-name", required=True)
|
|
@@ -52,7 +135,7 @@ def register(subparsers: argparse._SubParsersAction[argparse.ArgumentParser]) ->
|
|
|
52
135
|
member = builder_subparsers.add_parser("member", help="成员目录")
|
|
53
136
|
member_subparsers = member.add_subparsers(dest="builder_member_command", required=True)
|
|
54
137
|
member_search = member_subparsers.add_parser("search", help="搜索成员")
|
|
55
|
-
member_search.add_argument("--query",
|
|
138
|
+
member_search.add_argument("--query", default="")
|
|
56
139
|
member_search.add_argument("--page-num", type=int, default=1)
|
|
57
140
|
member_search.add_argument("--page-size", type=int, default=20)
|
|
58
141
|
member_search.add_argument("--contain-disable", action=argparse.BooleanOptionalAction, default=False)
|
|
@@ -61,7 +144,7 @@ def register(subparsers: argparse._SubParsersAction[argparse.ArgumentParser]) ->
|
|
|
61
144
|
role = builder_subparsers.add_parser("role", help="角色目录")
|
|
62
145
|
role_subparsers = role.add_subparsers(dest="builder_role_command", required=True)
|
|
63
146
|
role_search = role_subparsers.add_parser("search", help="搜索角色")
|
|
64
|
-
role_search.add_argument("--keyword",
|
|
147
|
+
role_search.add_argument("--keyword", default="")
|
|
65
148
|
role_search.add_argument("--page-num", type=int, default=1)
|
|
66
149
|
role_search.add_argument("--page-size", type=int, default=20)
|
|
67
150
|
role_search.set_defaults(handler=_handle_role_search, format_hint="builder_summary")
|
|
@@ -121,8 +204,7 @@ def register(subparsers: argparse._SubParsersAction[argparse.ArgumentParser]) ->
|
|
|
121
204
|
default="summary",
|
|
122
205
|
)
|
|
123
206
|
app_get.add_argument("--app-key", default="")
|
|
124
|
-
app_get.add_argument("--app-keys",
|
|
125
|
-
app_get.add_argument("--app-keys-file", help="JSON 字符串数组;用于批量读取同一 section")
|
|
207
|
+
app_get.add_argument("--app-keys", help="逗号分隔的多应用批量读取,例如: KEY1,KEY2,KEY3")
|
|
126
208
|
app_get.set_defaults(handler=_handle_app_get, format_hint="builder_summary")
|
|
127
209
|
|
|
128
210
|
app_repair_code_blocks = app_subparsers.add_parser("repair-code-blocks", help="扫描或修复现有代码块配置")
|
|
@@ -139,35 +221,34 @@ def register(subparsers: argparse._SubParsersAction[argparse.ArgumentParser]) ->
|
|
|
139
221
|
|
|
140
222
|
button_get = button_subparsers.add_parser("get", help="读取应用自定义按钮配置")
|
|
141
223
|
button_get.add_argument("--app-key", default="")
|
|
142
|
-
button_get.add_argument("--app-keys",
|
|
143
|
-
button_get.add_argument("--app-keys-file", help="JSON 字符串数组;用于批量读取按钮配置")
|
|
224
|
+
button_get.add_argument("--app-keys", help="逗号分隔的多应用批量读取,例如: KEY1,KEY2,KEY3")
|
|
144
225
|
button_get.set_defaults(handler=_handle_button_get, format_hint="builder_summary")
|
|
145
226
|
|
|
146
227
|
button_apply = button_subparsers.add_parser("apply", help="声明式创建、更新或删除自定义按钮")
|
|
147
228
|
button_apply.add_argument("--app-key", default="")
|
|
148
|
-
button_apply.add_argument("--apps-file", help="多应用按钮配置 JSON 数组;每项包含 app_key 和按钮 payload")
|
|
149
229
|
button_apply.add_argument("--upsert-buttons-file")
|
|
150
230
|
button_apply.add_argument("--patch-buttons-file")
|
|
151
231
|
button_apply.add_argument("--remove-buttons-file")
|
|
152
232
|
button_apply.add_argument("--view-configs-file")
|
|
233
|
+
button_apply.add_argument("--apps-file", help="多应用批量按钮 JSON 数组,每项含 app_key + upsert_buttons/patch_buttons/remove_buttons/view_configs")
|
|
153
234
|
button_apply.set_defaults(handler=_handle_button_apply, format_hint="builder_summary", force_json_output=True)
|
|
154
235
|
|
|
155
236
|
associated_resource = builder_subparsers.add_parser("associated-resource", aliases=["associated-resources"], help="关联视图/报表")
|
|
156
237
|
associated_resource_subparsers = associated_resource.add_subparsers(dest="builder_associated_resource_command", required=True)
|
|
238
|
+
|
|
157
239
|
associated_resource_get = associated_resource_subparsers.add_parser("get", help="读取应用关联资源配置")
|
|
158
240
|
associated_resource_get.add_argument("--app-key", default="")
|
|
159
|
-
associated_resource_get.add_argument("--app-keys",
|
|
160
|
-
associated_resource_get.add_argument("--app-keys-file", help="JSON 字符串数组;用于批量读取关联资源配置")
|
|
241
|
+
associated_resource_get.add_argument("--app-keys", help="逗号分隔的多应用批量读取,例如: KEY1,KEY2,KEY3")
|
|
161
242
|
associated_resource_get.set_defaults(handler=_handle_associated_resource_get, format_hint="builder_summary")
|
|
162
243
|
|
|
163
244
|
associated_resource_apply = associated_resource_subparsers.add_parser("apply", help="声明式管理应用关联资源池和视图展示配置")
|
|
164
245
|
associated_resource_apply.add_argument("--app-key", default="")
|
|
165
|
-
associated_resource_apply.add_argument("--apps-file", help="多应用关联资源配置 JSON 数组;每项包含 app_key 和资源 payload")
|
|
166
246
|
associated_resource_apply.add_argument("--upsert-resources-file")
|
|
167
247
|
associated_resource_apply.add_argument("--patch-resources-file")
|
|
168
248
|
associated_resource_apply.add_argument("--remove-associated-item-ids-file")
|
|
169
249
|
associated_resource_apply.add_argument("--reorder-associated-item-ids-file")
|
|
170
250
|
associated_resource_apply.add_argument("--view-configs-file")
|
|
251
|
+
associated_resource_apply.add_argument("--apps-file", help="多应用批量关联资源 JSON 数组,每项含 app_key + upsert_resources/patch_resources/remove_associated_item_ids/reorder_associated_item_ids/view_configs")
|
|
171
252
|
associated_resource_apply.set_defaults(handler=_handle_associated_resource_apply, format_hint="builder_summary", force_json_output=True)
|
|
172
253
|
|
|
173
254
|
portal = builder_subparsers.add_parser("portal", help="门户")
|
|
@@ -195,13 +276,9 @@ def register(subparsers: argparse._SubParsersAction[argparse.ArgumentParser]) ->
|
|
|
195
276
|
portal_apply.add_argument("--hide-copyright", action=argparse.BooleanOptionalAction, default=None)
|
|
196
277
|
portal_apply.add_argument("--dash-global-config-file")
|
|
197
278
|
portal_apply.add_argument("--config-file")
|
|
198
|
-
portal_apply.add_argument("--patch-sections-file", help="门户组件局部更新 JSON 数组,每项含 chart_ref/view_ref/order + set/unset")
|
|
279
|
+
portal_apply.add_argument("--patch-sections-file", help="门户组件局部更新 JSON 数组,每项含 chart 或 chart_ref/view_ref/order + set/unset")
|
|
199
280
|
portal_apply.set_defaults(handler=_handle_portal_apply, format_hint="builder_summary", force_json_output=True)
|
|
200
281
|
|
|
201
|
-
portal_delete = portal_subparsers.add_parser("delete", help="删除门户")
|
|
202
|
-
portal_delete.add_argument("--dash-key", required=True)
|
|
203
|
-
portal_delete.set_defaults(handler=_handle_portal_delete, format_hint="builder_summary", force_json_output=True)
|
|
204
|
-
|
|
205
282
|
schema_apply = builder_subparsers.add_parser("schema", help="字段搭建")
|
|
206
283
|
schema_apply_subparsers = schema_apply.add_subparsers(dest="builder_schema_command", required=True)
|
|
207
284
|
schema_apply_apply = schema_apply_subparsers.add_parser("apply", help="执行字段变更")
|
|
@@ -212,9 +289,9 @@ def register(subparsers: argparse._SubParsersAction[argparse.ArgumentParser]) ->
|
|
|
212
289
|
schema_apply_apply.add_argument("--icon")
|
|
213
290
|
schema_apply_apply.add_argument("--color")
|
|
214
291
|
schema_apply_apply.add_argument("--visibility-file")
|
|
215
|
-
schema_apply_apply.add_argument("--
|
|
216
|
-
schema_apply_apply.add_argument("--
|
|
217
|
-
schema_apply_apply.add_argument("--
|
|
292
|
+
schema_apply_apply.add_argument("--create-if-missing", action="store_true")
|
|
293
|
+
schema_apply_apply.add_argument("--publish", action=argparse.BooleanOptionalAction, default=True)
|
|
294
|
+
schema_apply_apply.add_argument("--apps-file", help="多应用 schema JSON;推荐对象 {package_id, apps},兼容 raw apps 数组;支持 relation target_app_ref")
|
|
218
295
|
schema_apply_apply.add_argument("--add-fields-file", help="字段 JSON 数组;字段可用 as_data_title/as_data_cover 标记数据标题/封面")
|
|
219
296
|
schema_apply_apply.add_argument("--update-fields-file", help="字段更新 JSON 数组;set 内可用 as_data_title/as_data_cover 标记数据标题/封面")
|
|
220
297
|
schema_apply_apply.add_argument("--remove-fields-file")
|
|
@@ -224,63 +301,61 @@ def register(subparsers: argparse._SubParsersAction[argparse.ArgumentParser]) ->
|
|
|
224
301
|
layout_apply_subparsers = layout_apply.add_subparsers(dest="builder_layout_command", required=True)
|
|
225
302
|
layout_apply_apply = layout_apply_subparsers.add_parser("apply", help="执行布局变更")
|
|
226
303
|
layout_apply_apply.add_argument("--app-key", default="")
|
|
227
|
-
layout_apply_apply.add_argument("--apps-file", help="多应用布局 JSON 数组;每项包含 app_key/mode?/publish?/sections")
|
|
228
304
|
layout_apply_apply.add_argument("--mode", choices=["merge", "replace"], default="merge")
|
|
229
305
|
layout_apply_apply.add_argument("--publish", action=argparse.BooleanOptionalAction, default=True)
|
|
230
306
|
layout_apply_apply.add_argument("--sections-file")
|
|
307
|
+
layout_apply_apply.add_argument("--apps-file", help="多应用批量布局 JSON 数组,每项含 app_key + sections")
|
|
231
308
|
layout_apply_apply.set_defaults(handler=_handle_layout_apply, format_hint="builder_summary", force_json_output=True)
|
|
232
309
|
|
|
233
310
|
views_apply = builder_subparsers.add_parser("views", help="视图")
|
|
234
311
|
views_apply_subparsers = views_apply.add_subparsers(dest="builder_views_command", required=True)
|
|
235
312
|
views_apply_apply = views_apply_subparsers.add_parser("apply", help="执行视图变更")
|
|
236
313
|
views_apply_apply.add_argument("--app-key", default="")
|
|
237
|
-
views_apply_apply.add_argument("--apps-file", help="多应用视图 JSON 数组;每项包含 app_key 和 upsert/patch/remove payload")
|
|
238
314
|
views_apply_apply.add_argument("--publish", action=argparse.BooleanOptionalAction, default=True)
|
|
239
315
|
views_apply_apply.add_argument("--upsert-views-file")
|
|
240
316
|
views_apply_apply.add_argument("--patch-views-file")
|
|
241
317
|
views_apply_apply.add_argument("--remove-views-file")
|
|
318
|
+
views_apply_apply.add_argument("--views-file", help="推荐:扁平批量视图 JSON;对象 {views:[{operation, app_key, ...}]},同 app/跨 app 均可")
|
|
319
|
+
views_apply_apply.add_argument("--apps-file", help="多应用批量视图 JSON 数组,每项含 app_key + upsert_views/patch_views/remove_views")
|
|
242
320
|
views_apply_apply.set_defaults(handler=_handle_views_apply, format_hint="builder_summary", force_json_output=True)
|
|
243
321
|
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
322
|
+
flow_parser = builder_subparsers.add_parser("flow", help="流程(WorkflowSpec)")
|
|
323
|
+
flow_subparsers = flow_parser.add_subparsers(dest="builder_flow_command", required=True)
|
|
324
|
+
|
|
325
|
+
flow_schema = flow_subparsers.add_parser("schema", help="读取 WorkflowSpec JSON Schema")
|
|
247
326
|
flow_schema.add_argument("--schema-version", default="")
|
|
248
327
|
flow_schema.set_defaults(handler=_handle_flow_schema, format_hint="builder_summary")
|
|
249
328
|
|
|
250
|
-
flow_get =
|
|
329
|
+
flow_get = flow_subparsers.add_parser("get", help="读取 WorkflowSpec")
|
|
251
330
|
flow_get.add_argument("--app-key", required=True)
|
|
252
331
|
flow_get.add_argument("--version-id", default="")
|
|
253
332
|
flow_get.set_defaults(handler=_handle_flow_get, format_hint="builder_summary")
|
|
254
333
|
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
flow_apply_apply.add_argument("--idempotency-key", default="")
|
|
264
|
-
flow_apply_apply.add_argument("--schema-version", default="")
|
|
265
|
-
flow_apply_apply.set_defaults(handler=_handle_flow_apply, format_hint="builder_summary", force_json_output=True)
|
|
334
|
+
flow_apply = flow_subparsers.add_parser("apply", help="应用 WorkflowSpec")
|
|
335
|
+
flow_apply.add_argument("--app-key", required=True)
|
|
336
|
+
flow_apply.add_argument("--spec-file")
|
|
337
|
+
flow_apply.add_argument("--patch-nodes-file", help="节点局部更新 JSON 数组,每项含 id + set/unset")
|
|
338
|
+
flow_apply.add_argument("--publish", action=argparse.BooleanOptionalAction, default=True)
|
|
339
|
+
flow_apply.add_argument("--idempotency-key", default="")
|
|
340
|
+
flow_apply.add_argument("--schema-version", default="")
|
|
341
|
+
flow_apply.set_defaults(handler=_handle_flow_apply, format_hint="builder_summary", force_json_output=True)
|
|
266
342
|
|
|
267
343
|
charts_apply = builder_subparsers.add_parser("charts", help="报表")
|
|
268
344
|
charts_apply_subparsers = charts_apply.add_subparsers(dest="builder_charts_command", required=True)
|
|
269
345
|
charts_apply_apply = charts_apply_subparsers.add_parser("apply", help="执行报表变更")
|
|
270
346
|
charts_apply_apply.add_argument("--app-key", default="")
|
|
271
|
-
charts_apply_apply.add_argument("--apps-file", help="多应用报表 JSON 数组;每项包含 app_key 和 upsert/patch/remove/reorder payload")
|
|
272
347
|
charts_apply_apply.add_argument("--upsert-file")
|
|
273
348
|
charts_apply_apply.add_argument("--patch-file")
|
|
274
349
|
charts_apply_apply.add_argument("--remove-chart-ids-file")
|
|
275
350
|
charts_apply_apply.add_argument("--reorder-chart-ids-file")
|
|
351
|
+
charts_apply_apply.add_argument("--apps-file", help="多应用批量报表 JSON 数组,每项含 app_key + upsert_charts/patch_charts/remove_chart_ids/reorder_chart_ids")
|
|
276
352
|
charts_apply_apply.set_defaults(handler=_handle_charts_apply, format_hint="builder_summary", force_json_output=True)
|
|
277
353
|
|
|
278
354
|
publish_verify = builder_subparsers.add_parser("publish", help="发布校验")
|
|
279
355
|
publish_verify_subparsers = publish_verify.add_subparsers(dest="builder_publish_command", required=True)
|
|
280
356
|
publish_verify_verify = publish_verify_subparsers.add_parser("verify", help="校验应用发布")
|
|
281
357
|
publish_verify_verify.add_argument("--app-key", default="")
|
|
282
|
-
publish_verify_verify.add_argument("--app-keys",
|
|
283
|
-
publish_verify_verify.add_argument("--app-keys-file", help="JSON 字符串数组;用于批量发布校验")
|
|
358
|
+
publish_verify_verify.add_argument("--app-keys", help="逗号分隔的多应用批量校验,例如: KEY1,KEY2,KEY3")
|
|
284
359
|
publish_verify_verify.add_argument("--expected-package-id", type=int)
|
|
285
360
|
publish_verify_verify.set_defaults(handler=_handle_publish_verify, format_hint="builder_summary", force_json_output=True)
|
|
286
361
|
|
|
@@ -385,7 +460,6 @@ def _handle_package_apply(args: argparse.Namespace, context: CliContext) -> dict
|
|
|
385
460
|
config = load_object_arg(args.config_file, option_name="--config-file")
|
|
386
461
|
if not isinstance(config, dict):
|
|
387
462
|
raise_config_error("package apply --config-file must contain a JSON object.")
|
|
388
|
-
config.pop("create_if_missing", None)
|
|
389
463
|
return context.builder.package_apply(profile=args.profile, **config)
|
|
390
464
|
|
|
391
465
|
|
|
@@ -429,9 +503,26 @@ def _handle_button_catalog(args: argparse.Namespace, context: CliContext) -> dic
|
|
|
429
503
|
|
|
430
504
|
|
|
431
505
|
def _handle_button_apply(args: argparse.Namespace, context: CliContext) -> dict:
|
|
432
|
-
apps = load_list_arg(args
|
|
433
|
-
if apps
|
|
434
|
-
|
|
506
|
+
apps = load_list_arg(getattr(args, "apps_file", None), option_name="--apps-file")
|
|
507
|
+
if apps:
|
|
508
|
+
single_app_args = [a for a in ["--app-key" if args.app_key else None, "--upsert-buttons-file" if getattr(args, "upsert_buttons_file", None) else None, "--patch-buttons-file" if getattr(args, "patch_buttons_file", None) else None, "--remove-buttons-file" if getattr(args, "remove_buttons_file", None) else None, "--view-configs-file" if getattr(args, "view_configs_file", None) else None] if a]
|
|
509
|
+
if single_app_args:
|
|
510
|
+
raise_config_error(
|
|
511
|
+
f"button apply --apps-file cannot be combined with {', '.join(single_app_args)}.",
|
|
512
|
+
fix_hint="Use --apps-file for batch mode (each item contains app_key + per-app params), or remove --apps-file for single-app mode.",
|
|
513
|
+
)
|
|
514
|
+
if apps:
|
|
515
|
+
return context.builder.app_custom_buttons_apply(
|
|
516
|
+
profile=args.profile,
|
|
517
|
+
app_key="",
|
|
518
|
+
upsert_buttons=[],
|
|
519
|
+
patch_buttons=[],
|
|
520
|
+
remove_buttons=[],
|
|
521
|
+
view_configs=[],
|
|
522
|
+
apps=apps,
|
|
523
|
+
)
|
|
524
|
+
if not args.app_key:
|
|
525
|
+
raise_config_error("button apply requires --app-key or --apps-file", fix_hint="Pass --app-key APP_KEY for single-app mode, or --apps-file for batch mode.")
|
|
435
526
|
return context.builder.app_custom_buttons_apply(
|
|
436
527
|
profile=args.profile,
|
|
437
528
|
app_key=args.app_key,
|
|
@@ -439,14 +530,40 @@ def _handle_button_apply(args: argparse.Namespace, context: CliContext) -> dict:
|
|
|
439
530
|
patch_buttons=load_list_arg(args.patch_buttons_file, option_name="--patch-buttons-file"),
|
|
440
531
|
remove_buttons=load_list_arg(args.remove_buttons_file, option_name="--remove-buttons-file"),
|
|
441
532
|
view_configs=load_list_arg(args.view_configs_file, option_name="--view-configs-file"),
|
|
442
|
-
apps=apps,
|
|
443
533
|
)
|
|
444
534
|
|
|
445
535
|
|
|
446
536
|
def _handle_associated_resource_apply(args: argparse.Namespace, context: CliContext) -> dict:
|
|
447
|
-
apps = load_list_arg(args
|
|
448
|
-
if apps
|
|
449
|
-
|
|
537
|
+
apps = load_list_arg(getattr(args, "apps_file", None), option_name="--apps-file")
|
|
538
|
+
if apps:
|
|
539
|
+
single_app_args = [
|
|
540
|
+
a for a in [
|
|
541
|
+
"--app-key" if args.app_key else None,
|
|
542
|
+
"--upsert-resources-file" if getattr(args, "upsert_resources_file", None) else None,
|
|
543
|
+
"--patch-resources-file" if getattr(args, "patch_resources_file", None) else None,
|
|
544
|
+
"--remove-associated-item-ids-file" if getattr(args, "remove_associated_item_ids_file", None) else None,
|
|
545
|
+
"--reorder-associated-item-ids-file" if getattr(args, "reorder_associated_item_ids_file", None) else None,
|
|
546
|
+
"--view-configs-file" if getattr(args, "view_configs_file", None) else None,
|
|
547
|
+
] if a
|
|
548
|
+
]
|
|
549
|
+
if single_app_args:
|
|
550
|
+
raise_config_error(
|
|
551
|
+
f"associated-resource apply --apps-file cannot be combined with {', '.join(single_app_args)}.",
|
|
552
|
+
fix_hint="Use --apps-file for batch mode (each item contains app_key + per-app params), or remove --apps-file for single-app mode.",
|
|
553
|
+
)
|
|
554
|
+
if apps:
|
|
555
|
+
return context.builder.app_associated_resources_apply(
|
|
556
|
+
profile=args.profile,
|
|
557
|
+
app_key="",
|
|
558
|
+
upsert_resources=[],
|
|
559
|
+
patch_resources=[],
|
|
560
|
+
remove_associated_item_ids=[],
|
|
561
|
+
reorder_associated_item_ids=[],
|
|
562
|
+
view_configs=[],
|
|
563
|
+
apps=apps,
|
|
564
|
+
)
|
|
565
|
+
if not args.app_key:
|
|
566
|
+
raise_config_error("associated-resource apply requires --app-key or --apps-file", fix_hint="Pass --app-key APP_KEY for single-app mode, or --apps-file for batch mode.")
|
|
450
567
|
return context.builder.app_associated_resources_apply(
|
|
451
568
|
profile=args.profile,
|
|
452
569
|
app_key=args.app_key,
|
|
@@ -455,26 +572,27 @@ def _handle_associated_resource_apply(args: argparse.Namespace, context: CliCont
|
|
|
455
572
|
remove_associated_item_ids=load_list_arg(args.remove_associated_item_ids_file, option_name="--remove-associated-item-ids-file"),
|
|
456
573
|
reorder_associated_item_ids=load_list_arg(args.reorder_associated_item_ids_file, option_name="--reorder-associated-item-ids-file"),
|
|
457
574
|
view_configs=load_list_arg(args.view_configs_file, option_name="--view-configs-file"),
|
|
458
|
-
apps=apps,
|
|
459
575
|
)
|
|
460
576
|
|
|
461
577
|
|
|
462
578
|
def _handle_button_get(args: argparse.Namespace, context: CliContext) -> dict:
|
|
463
|
-
app_keys =
|
|
464
|
-
if app_keys
|
|
465
|
-
return context.builder.app_get_buttons(profile=args.profile,
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
579
|
+
app_keys = _parse_app_keys_arg(getattr(args, "app_keys", None))
|
|
580
|
+
if app_keys:
|
|
581
|
+
return context.builder.app_get_buttons(profile=args.profile, app_keys=app_keys)
|
|
582
|
+
app_key = str(getattr(args, "app_key", "") or "").strip()
|
|
583
|
+
if not app_key:
|
|
584
|
+
raise_config_error("button get requires --app-key or --app-keys", fix_hint="Pass --app-key APP_KEY or --app-keys KEY1,KEY2")
|
|
585
|
+
return context.builder.app_get_buttons(profile=args.profile, app_key=app_key)
|
|
469
586
|
|
|
470
587
|
|
|
471
588
|
def _handle_associated_resource_get(args: argparse.Namespace, context: CliContext) -> dict:
|
|
472
|
-
app_keys =
|
|
473
|
-
if app_keys
|
|
474
|
-
return context.builder.app_get_associated_resources(profile=args.profile,
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
589
|
+
app_keys = _parse_app_keys_arg(getattr(args, "app_keys", None))
|
|
590
|
+
if app_keys:
|
|
591
|
+
return context.builder.app_get_associated_resources(profile=args.profile, app_keys=app_keys)
|
|
592
|
+
app_key = str(getattr(args, "app_key", "") or "").strip()
|
|
593
|
+
if not app_key:
|
|
594
|
+
raise_config_error("associated-resource get requires --app-key or --app-keys", fix_hint="Pass --app-key APP_KEY or --app-keys KEY1,KEY2")
|
|
595
|
+
return context.builder.app_get_associated_resources(profile=args.profile, app_key=app_key)
|
|
478
596
|
|
|
479
597
|
|
|
480
598
|
def _handle_button_create(args: argparse.Namespace, context: CliContext) -> dict:
|
|
@@ -498,18 +616,32 @@ def _handle_button_delete(args: argparse.Namespace, context: CliContext) -> dict
|
|
|
498
616
|
return context.builder.app_custom_button_delete(profile=args.profile, app_key=args.app_key, button_id=args.button_id)
|
|
499
617
|
|
|
500
618
|
|
|
501
|
-
def _app_keys_from_args(args: argparse.Namespace) -> list[str] | None:
|
|
502
|
-
keys: list[str] = []
|
|
503
|
-
raw = str(getattr(args, "app_keys", "") or "").strip()
|
|
504
|
-
if raw:
|
|
505
|
-
keys.extend(part.strip() for part in raw.split(",") if part.strip())
|
|
506
|
-
keys_file = getattr(args, "app_keys_file", None)
|
|
507
|
-
if keys_file:
|
|
508
|
-
keys.extend(str(item).strip() for item in require_list_arg(keys_file, option_name="--app-keys-file") if str(item).strip())
|
|
509
|
-
return keys or None
|
|
510
|
-
|
|
511
|
-
|
|
512
619
|
def _handle_app_get(args: argparse.Namespace, context: CliContext) -> dict:
|
|
620
|
+
section = args.builder_app_get_section
|
|
621
|
+
app_keys = _parse_app_keys_arg(getattr(args, "app_keys", None))
|
|
622
|
+
app_key = str(getattr(args, "app_key", "") or "").strip()
|
|
623
|
+
if not app_keys and not app_key:
|
|
624
|
+
raise_config_error("app get requires --app-key or --app-keys", fix_hint="Pass --app-key APP_KEY or --app-keys KEY1,KEY2")
|
|
625
|
+
if section == "buttons":
|
|
626
|
+
if app_keys:
|
|
627
|
+
return context.builder.app_get_buttons(profile=args.profile, app_keys=app_keys)
|
|
628
|
+
return context.builder.app_get_buttons(profile=args.profile, app_key=app_key)
|
|
629
|
+
if section == "associated-resources":
|
|
630
|
+
if app_keys:
|
|
631
|
+
return context.builder.app_get_associated_resources(profile=args.profile, app_keys=app_keys)
|
|
632
|
+
return context.builder.app_get_associated_resources(profile=args.profile, app_key=app_key)
|
|
633
|
+
if app_keys:
|
|
634
|
+
batch_handlers = {
|
|
635
|
+
"summary": context.builder.app_get,
|
|
636
|
+
"fields": context.builder.app_get_fields,
|
|
637
|
+
"layout": context.builder.app_get_layout,
|
|
638
|
+
"views": context.builder.app_get_views,
|
|
639
|
+
"flow": context.builder.app_get_flow,
|
|
640
|
+
"charts": context.builder.app_get_charts,
|
|
641
|
+
}
|
|
642
|
+
if section not in batch_handlers:
|
|
643
|
+
raise_config_error(f"app get --app-keys does not support section '{section}'", fix_hint="Batch reads support: summary, fields, layout, views, flow, charts, buttons, associated-resources")
|
|
644
|
+
return batch_handlers[section](profile=args.profile, app_keys=app_keys)
|
|
513
645
|
handlers = {
|
|
514
646
|
"summary": context.builder.app_get,
|
|
515
647
|
"fields": context.builder.app_get_fields,
|
|
@@ -517,15 +649,8 @@ def _handle_app_get(args: argparse.Namespace, context: CliContext) -> dict:
|
|
|
517
649
|
"views": context.builder.app_get_views,
|
|
518
650
|
"flow": context.builder.app_get_flow,
|
|
519
651
|
"charts": context.builder.app_get_charts,
|
|
520
|
-
"buttons": context.builder.app_get_buttons,
|
|
521
|
-
"associated-resources": context.builder.app_get_associated_resources,
|
|
522
652
|
}
|
|
523
|
-
|
|
524
|
-
if app_keys is not None:
|
|
525
|
-
return handlers[args.builder_app_get_section](profile=args.profile, app_key="", app_keys=app_keys)
|
|
526
|
-
if not (args.app_key or "").strip():
|
|
527
|
-
raise_config_error("builder app get requires --app-key unless --app-keys or --app-keys-file is provided")
|
|
528
|
-
return handlers[args.builder_app_get_section](profile=args.profile, app_key=args.app_key)
|
|
653
|
+
return handlers[section](profile=args.profile, app_key=app_key)
|
|
529
654
|
|
|
530
655
|
|
|
531
656
|
def _handle_app_repair_code_blocks(args: argparse.Namespace, context: CliContext) -> dict:
|
|
@@ -554,66 +679,49 @@ def _handle_chart_get(args: argparse.Namespace, context: CliContext) -> dict:
|
|
|
554
679
|
|
|
555
680
|
|
|
556
681
|
def _handle_schema_apply(args: argparse.Namespace, context: CliContext) -> dict:
|
|
557
|
-
|
|
558
|
-
apps = apps_payload["apps"]
|
|
559
|
-
apps_warnings = apps_payload.get("warnings") or []
|
|
560
|
-
package_id = args.package_id
|
|
561
|
-
if package_id is None and apps_payload.get("package_id") is not None:
|
|
562
|
-
package_id = _coerce_apps_file_package_id(apps_payload["package_id"])
|
|
682
|
+
apps, apps_package_id = _load_schema_apps_payload(args.apps_file, cli_package_id=args.package_id)
|
|
563
683
|
if args.apps_file:
|
|
564
|
-
file_publish = _coerce_apps_file_bool(
|
|
565
|
-
apps_payload.get("publish"),
|
|
566
|
-
field_name="publish",
|
|
567
|
-
default=True,
|
|
568
|
-
)
|
|
569
|
-
effective_publish = bool(args.publish if args.publish is not None else file_publish)
|
|
570
684
|
if not apps:
|
|
571
685
|
raise_config_error(
|
|
572
686
|
"schema apply multi-app mode requires a non-empty --apps-file.",
|
|
573
|
-
fix_hint=
|
|
574
|
-
error_code="APPS_FILE_EMPTY",
|
|
687
|
+
fix_hint='Pass {"package_id": 123, "apps": [{"client_key": "main", "app_name": "主表", "icon": "database", "color": "blue", "add_fields": []}]}',
|
|
575
688
|
)
|
|
576
|
-
if args.app_key or args.app_name or args.app_title or args.
|
|
689
|
+
if args.app_key or args.app_name or args.app_title or args.add_fields_file or args.update_fields_file or args.remove_fields_file:
|
|
577
690
|
raise_config_error(
|
|
578
|
-
"schema apply multi-app mode accepts --package-id plus --apps-file only.",
|
|
579
|
-
fix_hint="Use apps
|
|
691
|
+
"schema apply multi-app mode accepts --package-id/--create-if-missing plus --apps-file only.",
|
|
692
|
+
fix_hint="Use `--apps-file` for batch mode, or remove `--apps-file` and use the single-app arguments.",
|
|
580
693
|
)
|
|
581
|
-
if
|
|
694
|
+
if apps_package_id is None:
|
|
582
695
|
raise_config_error(
|
|
583
696
|
"schema apply multi-app mode requires --package-id.",
|
|
584
|
-
fix_hint="Pass `--package-id`, or
|
|
697
|
+
fix_hint="Pass root `--package-id`, or include package_id in the --apps-file object.",
|
|
585
698
|
)
|
|
586
|
-
|
|
699
|
+
return context.builder.app_schema_apply(
|
|
587
700
|
profile=args.profile,
|
|
588
|
-
package_id=
|
|
701
|
+
package_id=apps_package_id,
|
|
589
702
|
visibility=load_object_arg(args.visibility_file, option_name="--visibility-file"),
|
|
590
|
-
|
|
703
|
+
create_if_missing=bool(args.create_if_missing),
|
|
704
|
+
publish=bool(args.publish),
|
|
591
705
|
apps=apps,
|
|
592
|
-
form=None,
|
|
593
706
|
add_fields=[],
|
|
594
707
|
update_fields=[],
|
|
595
708
|
remove_fields=[],
|
|
596
709
|
)
|
|
597
|
-
if apps_warnings and isinstance(result, dict):
|
|
598
|
-
result_warnings = list(result.get("warnings") or [])
|
|
599
|
-
result_warnings.extend(deepcopy(apps_warnings))
|
|
600
|
-
result["warnings"] = result_warnings
|
|
601
|
-
return result
|
|
602
710
|
has_app_key = bool((args.app_key or "").strip())
|
|
603
711
|
has_app_name = bool((args.app_name or "").strip())
|
|
604
712
|
has_app_title = bool((args.app_title or "").strip())
|
|
605
713
|
has_package_id = args.package_id is not None
|
|
606
714
|
if has_app_key:
|
|
607
|
-
if has_package_id:
|
|
715
|
+
if args.create_if_missing or has_package_id:
|
|
608
716
|
raise_config_error(
|
|
609
717
|
"schema apply edit mode accepts --app-key and optional --app-name only.",
|
|
610
|
-
fix_hint="For existing apps, use `--app-key` and optionally `--app-name`. For create mode, use `--package-id --app-name`.",
|
|
718
|
+
fix_hint="For existing apps, use `--app-key` and optionally `--app-name`. For create mode, use `--package-id --app-name --create-if-missing`.",
|
|
611
719
|
)
|
|
612
720
|
else:
|
|
613
|
-
if not has_package_id or not (has_app_name or has_app_title):
|
|
721
|
+
if not args.create_if_missing or not has_package_id or not (has_app_name or has_app_title):
|
|
614
722
|
raise_config_error(
|
|
615
|
-
"schema apply create mode requires --package-id and --
|
|
616
|
-
fix_hint="Use `--app-key` for existing apps, or pass `--package-id --app-name` to create a new app.",
|
|
723
|
+
"schema apply create mode requires --package-id, --app-name, and --create-if-missing.",
|
|
724
|
+
fix_hint="Use `--app-key` for existing apps, or pass `--package-id --app-name --create-if-missing` to create a new app.",
|
|
617
725
|
)
|
|
618
726
|
return context.builder.app_schema_apply(
|
|
619
727
|
profile=args.profile,
|
|
@@ -624,175 +732,92 @@ def _handle_schema_apply(args: argparse.Namespace, context: CliContext) -> dict:
|
|
|
624
732
|
icon=args.icon,
|
|
625
733
|
color=args.color,
|
|
626
734
|
visibility=load_object_arg(args.visibility_file, option_name="--visibility-file"),
|
|
627
|
-
|
|
628
|
-
|
|
735
|
+
create_if_missing=bool(args.create_if_missing),
|
|
736
|
+
publish=bool(args.publish),
|
|
629
737
|
add_fields=load_list_arg(args.add_fields_file, option_name="--add-fields-file"),
|
|
630
738
|
update_fields=load_list_arg(args.update_fields_file, option_name="--update-fields-file"),
|
|
631
739
|
remove_fields=load_list_arg(args.remove_fields_file, option_name="--remove-fields-file"),
|
|
632
740
|
)
|
|
633
741
|
|
|
634
742
|
|
|
635
|
-
def
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
"apps": [
|
|
641
|
-
{
|
|
642
|
-
"client_key": "employee",
|
|
643
|
-
"app_name": "员工花名册",
|
|
644
|
-
"icon": "business-personalcard",
|
|
645
|
-
"color": "emerald",
|
|
646
|
-
"add_fields": [],
|
|
647
|
-
}
|
|
648
|
-
],
|
|
649
|
-
}
|
|
650
|
-
expected_shape_details = {
|
|
651
|
-
"expected_shape": expected_shape,
|
|
652
|
-
"expected_shape_json": (
|
|
653
|
-
'{"package_id":1001,"apps":[{"client_key":"employee","app_name":"员工花名册",'
|
|
654
|
-
'"icon":"business-personalcard","color":"emerald","add_fields":[]}]}'
|
|
655
|
-
),
|
|
656
|
-
}
|
|
657
|
-
payload = load_json_value(path, option_name="--apps-file")
|
|
658
|
-
if isinstance(payload, list):
|
|
659
|
-
if len(payload) == 1 and isinstance(payload[0], dict) and "apps" in payload[0]:
|
|
660
|
-
wrapper = payload[0]
|
|
661
|
-
apps = wrapper.get("apps")
|
|
662
|
-
if not isinstance(apps, list):
|
|
663
|
-
raise_config_error(
|
|
664
|
-
"--apps-file wrapper object requires an apps array.",
|
|
665
|
-
fix_hint="Use {\"package_id\":1001,\"apps\":[...]} or pass a raw apps array like [{\"app_name\":\"...\",\"icon\":\"...\",\"color\":\"...\",\"add_fields\":[...]}].",
|
|
666
|
-
error_code="APPS_FILE_SHAPE_INVALID",
|
|
667
|
-
details=expected_shape_details,
|
|
668
|
-
)
|
|
669
|
-
result: dict[str, object] = {
|
|
670
|
-
"apps": apps,
|
|
671
|
-
"warnings": [
|
|
672
|
-
{
|
|
673
|
-
"code": "APPS_FILE_WRAPPER_ARRAY_UNWRAPPED",
|
|
674
|
-
"message": "--apps-file was a singleton wrapper array; normalized it to {package_id, apps}.",
|
|
675
|
-
}
|
|
676
|
-
],
|
|
677
|
-
}
|
|
678
|
-
if wrapper.get("package_id") is not None:
|
|
679
|
-
result["package_id"] = wrapper.get("package_id")
|
|
680
|
-
if wrapper.get("publish") is not None:
|
|
681
|
-
result["publish"] = wrapper.get("publish")
|
|
682
|
-
return result
|
|
683
|
-
if any(isinstance(item, dict) and "apps" in item for item in payload):
|
|
684
|
-
raise_config_error(
|
|
685
|
-
"--apps-file root array items must be app items, not package wrapper objects.",
|
|
686
|
-
fix_hint="Use one object {\"package_id\":1001,\"apps\":[...]} or a raw app array [{\"app_name\":\"...\",\"icon\":\"...\",\"color\":\"...\",\"add_fields\":[...]}]. Do not wrap multiple {package_id, apps} objects in an array.",
|
|
687
|
-
error_code="APPS_FILE_SHAPE_INVALID",
|
|
688
|
-
details=expected_shape_details,
|
|
689
|
-
)
|
|
690
|
-
return {"apps": payload}
|
|
691
|
-
if isinstance(payload, dict):
|
|
692
|
-
apps = payload.get("apps")
|
|
693
|
-
if not isinstance(apps, list):
|
|
743
|
+
def _handle_layout_apply(args: argparse.Namespace, context: CliContext) -> dict:
|
|
744
|
+
apps = load_list_arg(getattr(args, "apps_file", None), option_name="--apps-file")
|
|
745
|
+
if apps:
|
|
746
|
+
single_app_args = [a for a in ["--app-key" if args.app_key else None, "--sections-file" if getattr(args, "sections_file", None) else None] if a]
|
|
747
|
+
if single_app_args:
|
|
694
748
|
raise_config_error(
|
|
695
|
-
"--apps-file
|
|
696
|
-
fix_hint="Use
|
|
697
|
-
error_code="APPS_FILE_SHAPE_INVALID",
|
|
698
|
-
details=expected_shape_details,
|
|
749
|
+
f"layout apply --apps-file cannot be combined with {', '.join(single_app_args)}.",
|
|
750
|
+
fix_hint="Use --apps-file for batch mode (each item contains app_key + sections), or remove --apps-file for single-app mode.",
|
|
699
751
|
)
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
fix_hint="Use [{...}] or {\"package_id\":1001,\"apps\":[...]}",
|
|
709
|
-
error_code="APPS_FILE_SHAPE_INVALID",
|
|
710
|
-
details=expected_shape_details,
|
|
711
|
-
)
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
def _coerce_apps_file_package_id(value: object) -> int:
|
|
715
|
-
package_id: int
|
|
716
|
-
if isinstance(value, bool):
|
|
717
|
-
_raise_apps_file_package_id_invalid(value)
|
|
718
|
-
if isinstance(value, int):
|
|
719
|
-
package_id = value
|
|
720
|
-
elif isinstance(value, str):
|
|
721
|
-
stripped = value.strip()
|
|
722
|
-
try:
|
|
723
|
-
package_id = int(stripped)
|
|
724
|
-
except ValueError:
|
|
725
|
-
_raise_apps_file_package_id_invalid(value)
|
|
726
|
-
else:
|
|
727
|
-
_raise_apps_file_package_id_invalid(value)
|
|
728
|
-
if package_id <= 0:
|
|
729
|
-
_raise_apps_file_package_id_invalid(value)
|
|
730
|
-
return package_id
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
def _raise_apps_file_package_id_invalid(value: object) -> None:
|
|
734
|
-
raise_config_error(
|
|
735
|
-
"--apps-file package_id must be a positive integer.",
|
|
736
|
-
fix_hint='Use a numeric package_id, for example {"package_id":1001,"apps":[...]}.',
|
|
737
|
-
error_code="APPS_FILE_PACKAGE_ID_INVALID",
|
|
738
|
-
details={
|
|
739
|
-
"field": "package_id",
|
|
740
|
-
"value": value,
|
|
741
|
-
"expected": "positive integer or numeric string",
|
|
742
|
-
},
|
|
743
|
-
)
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
def _coerce_apps_file_bool(value: object, *, field_name: str, default: bool) -> bool:
|
|
747
|
-
if value is None:
|
|
748
|
-
return default
|
|
749
|
-
if isinstance(value, bool):
|
|
750
|
-
return value
|
|
751
|
-
if isinstance(value, str):
|
|
752
|
-
try:
|
|
753
|
-
return parse_bool_text(value)
|
|
754
|
-
except argparse.ArgumentTypeError:
|
|
755
|
-
pass
|
|
756
|
-
raise_config_error(
|
|
757
|
-
f"--apps-file {field_name} must be a boolean.",
|
|
758
|
-
fix_hint=f'Use "{field_name}": true or "{field_name}": false in --apps-file.',
|
|
759
|
-
error_code="APPS_FILE_BOOLEAN_INVALID",
|
|
760
|
-
details={
|
|
761
|
-
"field": field_name,
|
|
762
|
-
"value": value,
|
|
763
|
-
"expected": "boolean true/false or string true/false/1/0/yes/no",
|
|
764
|
-
},
|
|
765
|
-
)
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
def _handle_layout_apply(args: argparse.Namespace, context: CliContext) -> dict:
|
|
769
|
-
apps = load_list_arg(args.apps_file, option_name="--apps-file") if getattr(args, "apps_file", None) else None
|
|
770
|
-
if apps is None and not (args.app_key or "").strip():
|
|
771
|
-
raise_config_error("builder layout apply requires --app-key unless --apps-file is provided")
|
|
772
|
-
if apps is None and not getattr(args, "sections_file", None):
|
|
773
|
-
raise_config_error(
|
|
774
|
-
"--sections-file is required",
|
|
775
|
-
fix_hint="Pass --sections-file for single-app layout apply, or use --apps-file for batch layout apply.",
|
|
776
|
-
error_code="ARGUMENT_ERROR",
|
|
777
|
-
details={
|
|
778
|
-
"prog": "qingflow builder layout apply",
|
|
779
|
-
"usage": "qingflow builder layout apply --app-key APP_KEY --sections-file SECTIONS.json",
|
|
780
|
-
},
|
|
752
|
+
if apps:
|
|
753
|
+
return context.builder.app_layout_apply(
|
|
754
|
+
profile=args.profile,
|
|
755
|
+
app_key="",
|
|
756
|
+
mode=args.mode,
|
|
757
|
+
publish=bool(args.publish),
|
|
758
|
+
sections=[],
|
|
759
|
+
apps=apps,
|
|
781
760
|
)
|
|
761
|
+
if not args.app_key:
|
|
762
|
+
raise_config_error("layout apply requires --app-key or --apps-file", fix_hint="Pass --app-key + --sections-file for single-app mode, or --apps-file for batch mode.")
|
|
782
763
|
return context.builder.app_layout_apply(
|
|
783
764
|
profile=args.profile,
|
|
784
765
|
app_key=args.app_key,
|
|
785
766
|
mode=args.mode,
|
|
786
767
|
publish=bool(args.publish),
|
|
787
|
-
sections=
|
|
788
|
-
apps=apps,
|
|
768
|
+
sections=require_list_arg(args.sections_file, option_name="--sections-file"),
|
|
789
769
|
)
|
|
790
770
|
|
|
791
771
|
|
|
792
772
|
def _handle_views_apply(args: argparse.Namespace, context: CliContext) -> dict:
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
773
|
+
views_file = getattr(args, "views_file", None)
|
|
774
|
+
views = _load_views_payload(views_file)
|
|
775
|
+
if views_file is not None:
|
|
776
|
+
legacy_args = [
|
|
777
|
+
a
|
|
778
|
+
for a in [
|
|
779
|
+
"--app-key" if args.app_key else None,
|
|
780
|
+
"--apps-file" if getattr(args, "apps_file", None) else None,
|
|
781
|
+
"--upsert-views-file" if getattr(args, "upsert_views_file", None) else None,
|
|
782
|
+
"--patch-views-file" if getattr(args, "patch_views_file", None) else None,
|
|
783
|
+
"--remove-views-file" if getattr(args, "remove_views_file", None) else None,
|
|
784
|
+
]
|
|
785
|
+
if a
|
|
786
|
+
]
|
|
787
|
+
if legacy_args:
|
|
788
|
+
raise_config_error(
|
|
789
|
+
f"views apply --views-file cannot be combined with {', '.join(legacy_args)}.",
|
|
790
|
+
fix_hint="Use --views-file as the single batch input, or remove --views-file and use legacy single-app/apps-file mode.",
|
|
791
|
+
)
|
|
792
|
+
return context.builder.app_views_apply(
|
|
793
|
+
profile=args.profile,
|
|
794
|
+
app_key="",
|
|
795
|
+
publish=bool(args.publish),
|
|
796
|
+
upsert_views=[],
|
|
797
|
+
patch_views=[],
|
|
798
|
+
remove_views=[],
|
|
799
|
+
views=views,
|
|
800
|
+
)
|
|
801
|
+
apps = load_list_arg(getattr(args, "apps_file", None), option_name="--apps-file")
|
|
802
|
+
if apps:
|
|
803
|
+
single_app_args = [a for a in ["--app-key" if args.app_key else None, "--upsert-views-file" if getattr(args, "upsert_views_file", None) else None, "--patch-views-file" if getattr(args, "patch_views_file", None) else None, "--remove-views-file" if getattr(args, "remove_views_file", None) else None] if a]
|
|
804
|
+
if single_app_args:
|
|
805
|
+
raise_config_error(
|
|
806
|
+
f"views apply --apps-file cannot be combined with {', '.join(single_app_args)}.",
|
|
807
|
+
fix_hint="Use --apps-file for batch mode (each item contains app_key + per-app params), or remove --apps-file for single-app mode.",
|
|
808
|
+
)
|
|
809
|
+
if apps:
|
|
810
|
+
return context.builder.app_views_apply(
|
|
811
|
+
profile=args.profile,
|
|
812
|
+
app_key="",
|
|
813
|
+
publish=bool(args.publish),
|
|
814
|
+
upsert_views=[],
|
|
815
|
+
patch_views=[],
|
|
816
|
+
remove_views=[],
|
|
817
|
+
apps=apps,
|
|
818
|
+
)
|
|
819
|
+
if not args.app_key:
|
|
820
|
+
raise_config_error("views apply requires --app-key or --apps-file", fix_hint="Pass --app-key for single-app mode, or --apps-file for batch mode.")
|
|
796
821
|
return context.builder.app_views_apply(
|
|
797
822
|
profile=args.profile,
|
|
798
823
|
app_key=args.app_key,
|
|
@@ -800,65 +825,79 @@ def _handle_views_apply(args: argparse.Namespace, context: CliContext) -> dict:
|
|
|
800
825
|
upsert_views=load_list_arg(args.upsert_views_file, option_name="--upsert-views-file"),
|
|
801
826
|
patch_views=load_list_arg(args.patch_views_file, option_name="--patch-views-file"),
|
|
802
827
|
remove_views=load_list_arg(args.remove_views_file, option_name="--remove-views-file"),
|
|
803
|
-
|
|
828
|
+
)
|
|
829
|
+
|
|
830
|
+
|
|
831
|
+
def _handle_flow_schema(args: argparse.Namespace, context: CliContext) -> dict:
|
|
832
|
+
return context.builder.app_flow_get_schema(
|
|
833
|
+
profile=args.profile,
|
|
834
|
+
schema_version=args.schema_version or None,
|
|
835
|
+
)
|
|
836
|
+
|
|
837
|
+
|
|
838
|
+
def _handle_flow_get(args: argparse.Namespace, context: CliContext) -> dict:
|
|
839
|
+
return context.builder.app_get_flow(
|
|
840
|
+
profile=args.profile,
|
|
841
|
+
app_key=args.app_key,
|
|
842
|
+
version_id=args.version_id or None,
|
|
804
843
|
)
|
|
805
844
|
|
|
806
845
|
|
|
807
846
|
def _handle_flow_apply(args: argparse.Namespace, context: CliContext) -> dict:
|
|
808
847
|
patch_nodes = load_list_arg(getattr(args, "patch_nodes_file", None), option_name="--patch-nodes-file")
|
|
809
|
-
|
|
810
|
-
has_legacy_nodes = bool(getattr(args, "nodes_file", None) or getattr(args, "transitions_file", None))
|
|
811
|
-
selected_modes = sum(1 for selected in (bool(patch_nodes), bool(spec), has_legacy_nodes) if selected)
|
|
812
|
-
if selected_modes != 1:
|
|
848
|
+
if patch_nodes and args.spec_file:
|
|
813
849
|
raise_config_error(
|
|
814
|
-
"
|
|
815
|
-
fix_hint="Use --
|
|
850
|
+
"flow apply --spec-file and --patch-nodes-file are mutually exclusive.",
|
|
851
|
+
fix_hint="Use --spec-file to replace the full flow spec, or --patch-nodes-file to patch specific nodes.",
|
|
816
852
|
)
|
|
817
853
|
if patch_nodes:
|
|
818
854
|
return context.builder.app_flow_apply(
|
|
819
855
|
profile=args.profile,
|
|
820
856
|
app_key=args.app_key,
|
|
821
857
|
publish=bool(args.publish),
|
|
858
|
+
spec=None,
|
|
822
859
|
patch_nodes=patch_nodes,
|
|
823
|
-
idempotency_key=args.idempotency_key,
|
|
824
|
-
schema_version=args.schema_version,
|
|
825
|
-
)
|
|
826
|
-
if spec:
|
|
827
|
-
return context.builder.app_flow_apply(
|
|
828
|
-
profile=args.profile,
|
|
829
|
-
app_key=args.app_key,
|
|
830
|
-
publish=bool(args.publish),
|
|
831
|
-
spec=spec,
|
|
832
|
-
idempotency_key=args.idempotency_key,
|
|
833
|
-
schema_version=args.schema_version,
|
|
834
|
-
)
|
|
835
|
-
if not getattr(args, "nodes_file", None) or not getattr(args, "transitions_file", None):
|
|
836
|
-
raise_config_error(
|
|
837
|
-
"legacy builder flow apply requires both --nodes-file and --transitions-file.",
|
|
838
|
-
fix_hint="Pass both files, or use --spec-file / --patch-nodes-file.",
|
|
860
|
+
idempotency_key=args.idempotency_key or None,
|
|
861
|
+
schema_version=args.schema_version or None,
|
|
839
862
|
)
|
|
863
|
+
spec_payload = load_object_arg(args.spec_file, option_name="--spec-file") if args.spec_file else None
|
|
864
|
+
if not isinstance(spec_payload, dict):
|
|
865
|
+
raise_config_error("flow apply requires either --spec-file or --patch-nodes-file.", fix_hint="Pass --spec-file to replace the full flow spec, or --patch-nodes-file to patch specific nodes.")
|
|
866
|
+
if "spec" in spec_payload and isinstance(spec_payload.get("spec"), dict):
|
|
867
|
+
spec = spec_payload["spec"]
|
|
868
|
+
else:
|
|
869
|
+
spec = spec_payload
|
|
840
870
|
return context.builder.app_flow_apply(
|
|
841
871
|
profile=args.profile,
|
|
842
872
|
app_key=args.app_key,
|
|
843
|
-
mode=args.mode,
|
|
844
873
|
publish=bool(args.publish),
|
|
845
|
-
|
|
846
|
-
|
|
874
|
+
spec=spec,
|
|
875
|
+
idempotency_key=args.idempotency_key or None,
|
|
876
|
+
schema_version=args.schema_version or None,
|
|
847
877
|
)
|
|
848
878
|
|
|
849
879
|
|
|
850
|
-
def _handle_flow_schema(args: argparse.Namespace, context: CliContext) -> dict:
|
|
851
|
-
return context.builder.app_flow_get_schema(profile=args.profile, schema_version=args.schema_version)
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
def _handle_flow_get(args: argparse.Namespace, context: CliContext) -> dict:
|
|
855
|
-
return context.builder.app_flow_get(profile=args.profile, app_key=args.app_key, version_id=args.version_id)
|
|
856
|
-
|
|
857
|
-
|
|
858
880
|
def _handle_charts_apply(args: argparse.Namespace, context: CliContext) -> dict:
|
|
859
|
-
apps = load_list_arg(args
|
|
860
|
-
if apps
|
|
861
|
-
|
|
881
|
+
apps = load_list_arg(getattr(args, "apps_file", None), option_name="--apps-file")
|
|
882
|
+
if apps:
|
|
883
|
+
single_app_args = [a for a in ["--app-key" if args.app_key else None, "--upsert-file" if getattr(args, "upsert_file", None) else None, "--patch-file" if getattr(args, "patch_file", None) else None, "--remove-chart-ids-file" if getattr(args, "remove_chart_ids_file", None) else None, "--reorder-chart-ids-file" if getattr(args, "reorder_chart_ids_file", None) else None] if a]
|
|
884
|
+
if single_app_args:
|
|
885
|
+
raise_config_error(
|
|
886
|
+
f"charts apply --apps-file cannot be combined with {', '.join(single_app_args)}.",
|
|
887
|
+
fix_hint="Use --apps-file for batch mode (each item contains app_key + per-app params), or remove --apps-file for single-app mode.",
|
|
888
|
+
)
|
|
889
|
+
if apps:
|
|
890
|
+
return context.builder.app_charts_apply(
|
|
891
|
+
profile=args.profile,
|
|
892
|
+
app_key="",
|
|
893
|
+
upsert_charts=[],
|
|
894
|
+
patch_charts=[],
|
|
895
|
+
remove_chart_ids=[],
|
|
896
|
+
reorder_chart_ids=[],
|
|
897
|
+
apps=apps,
|
|
898
|
+
)
|
|
899
|
+
if not args.app_key:
|
|
900
|
+
raise_config_error("charts apply requires --app-key or --apps-file", fix_hint="Pass --app-key for single-app mode, or --apps-file for batch mode.")
|
|
862
901
|
return context.builder.app_charts_apply(
|
|
863
902
|
profile=args.profile,
|
|
864
903
|
app_key=args.app_key,
|
|
@@ -866,11 +905,30 @@ def _handle_charts_apply(args: argparse.Namespace, context: CliContext) -> dict:
|
|
|
866
905
|
patch_charts=load_list_arg(args.patch_file, option_name="--patch-file"),
|
|
867
906
|
remove_chart_ids=load_list_arg(args.remove_chart_ids_file, option_name="--remove-chart-ids-file"),
|
|
868
907
|
reorder_chart_ids=load_list_arg(args.reorder_chart_ids_file, option_name="--reorder-chart-ids-file"),
|
|
869
|
-
apps=apps,
|
|
870
908
|
)
|
|
871
909
|
|
|
872
910
|
|
|
873
911
|
def _handle_portal_apply(args: argparse.Namespace, context: CliContext) -> dict:
|
|
912
|
+
patch_sections = load_list_arg(getattr(args, "patch_sections_file", None), option_name="--patch-sections-file")
|
|
913
|
+
if patch_sections:
|
|
914
|
+
if not (args.dash_key or "").strip():
|
|
915
|
+
raise_config_error("portal apply --patch-sections-file requires --dash-key", fix_hint="Pass --dash-key DASH_KEY to identify the portal to patch")
|
|
916
|
+
has_sections_file = bool(getattr(args, "sections_file", None))
|
|
917
|
+
has_payload_file = bool(getattr(args, "payload_file", None))
|
|
918
|
+
if has_sections_file or has_payload_file:
|
|
919
|
+
raise_config_error(
|
|
920
|
+
"portal apply --patch-sections-file cannot be combined with --sections-file or --payload-file.",
|
|
921
|
+
fix_hint="Use --patch-sections-file alone to patch specific sections, or --sections-file to replace all sections.",
|
|
922
|
+
)
|
|
923
|
+
return context.builder.portal_apply(
|
|
924
|
+
profile=args.profile,
|
|
925
|
+
dash_key=args.dash_key,
|
|
926
|
+
dash_name="",
|
|
927
|
+
package_id=None,
|
|
928
|
+
publish=bool(args.publish),
|
|
929
|
+
sections=[],
|
|
930
|
+
patch_sections=patch_sections,
|
|
931
|
+
)
|
|
874
932
|
payload = load_object_arg(args.payload_file, option_name="--payload-file") if getattr(args, "payload_file", None) else None
|
|
875
933
|
payload_obj = payload if isinstance(payload, dict) else {}
|
|
876
934
|
effective_dash_name = (args.dash_name or str(payload_obj.get("dash_name") or payload_obj.get("dashName") or payload_obj.get("name") or "")).strip()
|
|
@@ -889,11 +947,6 @@ def _handle_portal_apply(args: argparse.Namespace, context: CliContext) -> dict:
|
|
|
889
947
|
fix_hint="Use `--dash-key` for an existing portal. For create mode, pass `--package-id --dash-name`.",
|
|
890
948
|
)
|
|
891
949
|
sections = [] if not args.sections_file else require_list_arg(args.sections_file, option_name="--sections-file")
|
|
892
|
-
patch_sections = (
|
|
893
|
-
load_list_arg(args.patch_sections_file, option_name="--patch-sections-file")
|
|
894
|
-
if args.patch_sections_file
|
|
895
|
-
else None
|
|
896
|
-
)
|
|
897
950
|
return context.builder.portal_apply(
|
|
898
951
|
profile=args.profile,
|
|
899
952
|
dash_key=args.dash_key,
|
|
@@ -910,28 +963,17 @@ def _handle_portal_apply(args: argparse.Namespace, context: CliContext) -> dict:
|
|
|
910
963
|
dash_global_config=load_object_arg(args.dash_global_config_file, option_name="--dash-global-config-file"),
|
|
911
964
|
config=load_object_arg(args.config_file, option_name="--config-file"),
|
|
912
965
|
payload=payload,
|
|
913
|
-
patch_sections=patch_sections,
|
|
914
|
-
)
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
def _handle_portal_delete(args: argparse.Namespace, context: CliContext) -> dict:
|
|
918
|
-
return context.builder.portal_delete(
|
|
919
|
-
profile=args.profile,
|
|
920
|
-
dash_key=args.dash_key,
|
|
921
966
|
)
|
|
922
967
|
|
|
923
968
|
|
|
924
969
|
def _handle_publish_verify(args: argparse.Namespace, context: CliContext) -> dict:
|
|
925
|
-
app_keys =
|
|
926
|
-
if app_keys
|
|
970
|
+
app_keys = _parse_app_keys_arg(getattr(args, "app_keys", None))
|
|
971
|
+
if app_keys:
|
|
927
972
|
return context.builder.app_publish_verify(
|
|
928
973
|
profile=args.profile,
|
|
929
|
-
app_key="",
|
|
930
974
|
app_keys=app_keys,
|
|
931
975
|
expected_package_id=args.expected_package_id,
|
|
932
976
|
)
|
|
933
|
-
if not (args.app_key or "").strip():
|
|
934
|
-
raise_config_error("builder publish verify requires --app-key unless --app-keys or --app-keys-file is provided")
|
|
935
977
|
return context.builder.app_publish_verify(
|
|
936
978
|
profile=args.profile,
|
|
937
979
|
app_key=args.app_key,
|