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