@josephyan/qingflow-cli 0.2.0-beta.55 → 0.2.0-beta.56
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 +2 -2
- package/package.json +1 -1
- package/pyproject.toml +1 -1
- package/src/qingflow_mcp/cli/commands/app.py +16 -16
- package/src/qingflow_mcp/cli/commands/auth.py +16 -19
- package/src/qingflow_mcp/cli/commands/builder.py +139 -124
- package/src/qingflow_mcp/cli/commands/common.py +95 -21
- package/src/qingflow_mcp/cli/commands/imports.py +34 -42
- package/src/qingflow_mcp/cli/commands/record.py +133 -131
- package/src/qingflow_mcp/cli/commands/task.py +44 -43
- package/src/qingflow_mcp/cli/commands/workspace.py +10 -10
- package/src/qingflow_mcp/cli/context.py +32 -35
- package/src/qingflow_mcp/cli/formatters.py +121 -124
- package/src/qingflow_mcp/cli/main.py +17 -52
- package/src/qingflow_mcp/ops/__init__.py +3 -0
- package/src/qingflow_mcp/ops/apps.py +64 -0
- package/src/qingflow_mcp/ops/auth.py +121 -0
- package/src/qingflow_mcp/ops/base.py +290 -0
- package/src/qingflow_mcp/ops/builder.py +323 -0
- package/src/qingflow_mcp/ops/context.py +120 -0
- package/src/qingflow_mcp/ops/directory.py +171 -0
- package/src/qingflow_mcp/ops/feedback.py +49 -0
- package/src/qingflow_mcp/ops/files.py +78 -0
- package/src/qingflow_mcp/ops/imports.py +140 -0
- package/src/qingflow_mcp/ops/records.py +415 -0
- package/src/qingflow_mcp/ops/tasks.py +171 -0
- package/src/qingflow_mcp/ops/workspace.py +76 -0
- package/src/qingflow_mcp/server_app_builder.py +190 -122
- package/src/qingflow_mcp/server_app_user.py +662 -63
package/README.md
CHANGED
|
@@ -3,13 +3,13 @@
|
|
|
3
3
|
Install:
|
|
4
4
|
|
|
5
5
|
```bash
|
|
6
|
-
npm install @josephyan/qingflow-cli@0.2.0-beta.
|
|
6
|
+
npm install @josephyan/qingflow-cli@0.2.0-beta.56
|
|
7
7
|
```
|
|
8
8
|
|
|
9
9
|
Run:
|
|
10
10
|
|
|
11
11
|
```bash
|
|
12
|
-
npx -y -p @josephyan/qingflow-cli@0.2.0-beta.
|
|
12
|
+
npx -y -p @josephyan/qingflow-cli@0.2.0-beta.56 qingflow
|
|
13
13
|
```
|
|
14
14
|
|
|
15
15
|
Environment:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@josephyan/qingflow-cli",
|
|
3
|
-
"version": "0.2.0-beta.
|
|
3
|
+
"version": "0.2.0-beta.56",
|
|
4
4
|
"description": "Human-friendly Qingflow command line interface for auth, record operations, import, tasks, and stable builder flows.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
package/pyproject.toml
CHANGED
|
@@ -6,29 +6,29 @@ from ..context import CliContext
|
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
def register(subparsers: argparse._SubParsersAction[argparse.ArgumentParser]) -> None:
|
|
9
|
-
parser = subparsers.add_parser("
|
|
10
|
-
app_subparsers = parser.add_subparsers(dest="
|
|
9
|
+
parser = subparsers.add_parser("apps", help="应用发现")
|
|
10
|
+
app_subparsers = parser.add_subparsers(dest="apps_command", required=True)
|
|
11
11
|
|
|
12
12
|
list_parser = app_subparsers.add_parser("list", help="列出可见应用")
|
|
13
|
-
list_parser.set_defaults(handler=_handle_list, format_hint="
|
|
13
|
+
list_parser.set_defaults(handler=_handle_list, format_hint="apps_list")
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
15
|
+
find = app_subparsers.add_parser("find", help="搜索应用")
|
|
16
|
+
find.add_argument("--keyword", default="")
|
|
17
|
+
find.add_argument("--page", type=int, default=1)
|
|
18
|
+
find.add_argument("--page-size", type=int, default=50)
|
|
19
|
+
find.set_defaults(handler=_handle_find, format_hint="apps_list")
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
21
|
+
show = app_subparsers.add_parser("show", help="读取应用信息")
|
|
22
|
+
show.add_argument("--app", required=True)
|
|
23
|
+
show.set_defaults(handler=_handle_show, format_hint="app_show")
|
|
24
24
|
|
|
25
25
|
|
|
26
26
|
def _handle_list(args: argparse.Namespace, context: CliContext) -> dict:
|
|
27
|
-
return context.
|
|
27
|
+
return context.apps.list(profile=args.profile)
|
|
28
28
|
|
|
29
29
|
|
|
30
|
-
def
|
|
31
|
-
return context.
|
|
30
|
+
def _handle_find(args: argparse.Namespace, context: CliContext) -> dict:
|
|
31
|
+
return context.apps.find(
|
|
32
32
|
profile=args.profile,
|
|
33
33
|
keyword=args.keyword,
|
|
34
34
|
page_num=args.page,
|
|
@@ -36,5 +36,5 @@ def _handle_search(args: argparse.Namespace, context: CliContext) -> dict:
|
|
|
36
36
|
)
|
|
37
37
|
|
|
38
38
|
|
|
39
|
-
def
|
|
40
|
-
return context.
|
|
39
|
+
def _handle_show(args: argparse.Namespace, context: CliContext) -> dict:
|
|
40
|
+
return context.apps.show(profile=args.profile, app_key=args.app)
|
|
@@ -10,33 +10,34 @@ from .common import read_secret_arg
|
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
def register(subparsers: argparse._SubParsersAction[argparse.ArgumentParser]) -> None:
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
me = subparsers.add_parser("me", help="查看当前会话")
|
|
14
|
+
me.set_defaults(handler=_handle_me, format_hint="me")
|
|
15
15
|
|
|
16
|
-
login =
|
|
16
|
+
login = subparsers.add_parser("login", help="邮箱密码登录")
|
|
17
17
|
login.add_argument("--base-url")
|
|
18
18
|
login.add_argument("--qf-version")
|
|
19
19
|
login.add_argument("--email", required=True)
|
|
20
20
|
login.add_argument("--password")
|
|
21
21
|
login.add_argument("--password-stdin", action="store_true")
|
|
22
22
|
login.add_argument("--persist", action=argparse.BooleanOptionalAction, default=True)
|
|
23
|
-
login.set_defaults(handler=_handle_login, format_hint="
|
|
23
|
+
login.set_defaults(handler=_handle_login, format_hint="session")
|
|
24
24
|
|
|
25
|
-
use_token =
|
|
25
|
+
use_token = subparsers.add_parser("use-token", help="直接接入 token")
|
|
26
26
|
use_token.add_argument("--base-url")
|
|
27
27
|
use_token.add_argument("--qf-version")
|
|
28
28
|
use_token.add_argument("--token")
|
|
29
29
|
use_token.add_argument("--token-stdin", action="store_true")
|
|
30
|
-
use_token.add_argument("--ws
|
|
30
|
+
use_token.add_argument("--ws", dest="ws_id", type=int)
|
|
31
31
|
use_token.add_argument("--persist", action=argparse.BooleanOptionalAction, default=False)
|
|
32
|
-
use_token.set_defaults(handler=_handle_use_token, format_hint="
|
|
32
|
+
use_token.set_defaults(handler=_handle_use_token, format_hint="session")
|
|
33
33
|
|
|
34
|
-
|
|
35
|
-
whoami.set_defaults(handler=_handle_whoami, format_hint="auth_whoami")
|
|
36
|
-
|
|
37
|
-
logout = auth_subparsers.add_parser("logout", help="退出登录")
|
|
34
|
+
logout = subparsers.add_parser("logout", help="退出当前会话")
|
|
38
35
|
logout.add_argument("--forget-persisted", action="store_true")
|
|
39
|
-
logout.set_defaults(handler=_handle_logout, format_hint="")
|
|
36
|
+
logout.set_defaults(handler=_handle_logout, format_hint="session")
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _handle_me(args: argparse.Namespace, context: CliContext) -> dict:
|
|
40
|
+
return context.auth.me(profile=args.profile)
|
|
40
41
|
|
|
41
42
|
|
|
42
43
|
def _handle_login(args: argparse.Namespace, context: CliContext) -> dict:
|
|
@@ -48,7 +49,7 @@ def _handle_login(args: argparse.Namespace, context: CliContext) -> dict:
|
|
|
48
49
|
password = getpass.getpass("Password: ")
|
|
49
50
|
else:
|
|
50
51
|
raise QingflowApiError.config_error("password is required; use --password or --password-stdin")
|
|
51
|
-
return context.auth.
|
|
52
|
+
return context.auth.login(
|
|
52
53
|
profile=args.profile,
|
|
53
54
|
base_url=args.base_url,
|
|
54
55
|
qf_version=args.qf_version,
|
|
@@ -60,7 +61,7 @@ def _handle_login(args: argparse.Namespace, context: CliContext) -> dict:
|
|
|
60
61
|
|
|
61
62
|
def _handle_use_token(args: argparse.Namespace, context: CliContext) -> dict:
|
|
62
63
|
token = read_secret_arg(args.token, stdin_enabled=bool(args.token_stdin), label="token")
|
|
63
|
-
return context.auth.
|
|
64
|
+
return context.auth.use_token(
|
|
64
65
|
profile=args.profile,
|
|
65
66
|
base_url=args.base_url,
|
|
66
67
|
qf_version=args.qf_version,
|
|
@@ -70,9 +71,5 @@ def _handle_use_token(args: argparse.Namespace, context: CliContext) -> dict:
|
|
|
70
71
|
)
|
|
71
72
|
|
|
72
73
|
|
|
73
|
-
def _handle_whoami(args: argparse.Namespace, context: CliContext) -> dict:
|
|
74
|
-
return context.auth.auth_whoami(profile=args.profile)
|
|
75
|
-
|
|
76
|
-
|
|
77
74
|
def _handle_logout(args: argparse.Namespace, context: CliContext) -> dict:
|
|
78
|
-
return context.auth.
|
|
75
|
+
return context.auth.logout(profile=args.profile, forget_persisted=bool(args.forget_persisted))
|
|
@@ -1,184 +1,199 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import argparse
|
|
4
|
+
from typing import Any
|
|
4
5
|
|
|
6
|
+
from ...errors import QingflowApiError
|
|
5
7
|
from ..context import CliContext
|
|
6
|
-
from .common import
|
|
8
|
+
from .common import add_file_arg, add_stdin_json_flag, load_object_input
|
|
7
9
|
|
|
8
10
|
|
|
9
11
|
def register(subparsers: argparse._SubParsersAction[argparse.ArgumentParser]) -> None:
|
|
10
|
-
parser = subparsers.add_parser("
|
|
11
|
-
|
|
12
|
+
parser = subparsers.add_parser("build", help="稳定搭建能力")
|
|
13
|
+
build_subparsers = parser.add_subparsers(dest="build_command", required=True)
|
|
12
14
|
|
|
13
|
-
package =
|
|
14
|
-
package_subparsers = package.add_subparsers(dest="
|
|
15
|
+
package = build_subparsers.add_parser("package", help="应用包")
|
|
16
|
+
package_subparsers = package.add_subparsers(dest="build_package_command", required=True)
|
|
15
17
|
package_resolve = package_subparsers.add_parser("resolve", help="解析应用包")
|
|
16
|
-
package_resolve.add_argument("--
|
|
17
|
-
package_resolve.set_defaults(handler=_handle_package_resolve, format_hint="
|
|
18
|
-
|
|
19
|
-
app = builder_subparsers.add_parser("app", help="应用")
|
|
20
|
-
app_subparsers = app.add_subparsers(dest="builder_app_command", required=True)
|
|
18
|
+
package_resolve.add_argument("--name", required=True)
|
|
19
|
+
package_resolve.set_defaults(handler=_handle_package_resolve, format_hint="builder_result")
|
|
21
20
|
|
|
21
|
+
app = build_subparsers.add_parser("app", help="应用")
|
|
22
|
+
app_subparsers = app.add_subparsers(dest="build_app_command", required=True)
|
|
22
23
|
app_resolve = app_subparsers.add_parser("resolve", help="解析应用")
|
|
23
24
|
app_resolve.add_argument("--app-key", default="")
|
|
24
|
-
app_resolve.add_argument("--
|
|
25
|
+
app_resolve.add_argument("--name", default="")
|
|
25
26
|
app_resolve.add_argument("--package-tag-id", type=int)
|
|
26
|
-
app_resolve.set_defaults(handler=_handle_app_resolve, format_hint="
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
27
|
+
app_resolve.set_defaults(handler=_handle_app_resolve, format_hint="builder_result")
|
|
28
|
+
|
|
29
|
+
app_show = app_subparsers.add_parser("show", help="读取应用摘要")
|
|
30
|
+
app_show.add_argument("--app", required=True)
|
|
31
|
+
app_show.set_defaults(handler=_handle_app_show, format_hint="builder_result")
|
|
32
|
+
|
|
33
|
+
fields = build_subparsers.add_parser("fields", help="字段")
|
|
34
|
+
fields_subparsers = fields.add_subparsers(dest="build_fields_command", required=True)
|
|
35
|
+
fields_show = fields_subparsers.add_parser("show", help="读取字段摘要")
|
|
36
|
+
fields_show.add_argument("--app", required=True)
|
|
37
|
+
fields_show.set_defaults(handler=_handle_fields_show, format_hint="builder_result")
|
|
38
|
+
fields_apply = fields_subparsers.add_parser("apply", help="执行字段变更")
|
|
39
|
+
fields_apply.add_argument("--app", default="")
|
|
40
|
+
fields_apply.add_argument("--package-tag-id", type=int)
|
|
41
|
+
fields_apply.add_argument("--name", dest="app_name", default="")
|
|
42
|
+
fields_apply.add_argument("--title", dest="app_title", default="")
|
|
43
|
+
fields_apply.add_argument("--create-if-missing", action="store_true")
|
|
44
|
+
fields_apply.add_argument("--publish", action=argparse.BooleanOptionalAction, default=True)
|
|
45
|
+
add_file_arg(fields_apply, required=False, help_text="读取字段变更 JSON,支持 add_fields/update_fields/remove_fields")
|
|
46
|
+
add_stdin_json_flag(fields_apply)
|
|
47
|
+
fields_apply.set_defaults(handler=_handle_fields_apply, format_hint="builder_result")
|
|
48
|
+
|
|
49
|
+
layout = build_subparsers.add_parser("layout", help="布局")
|
|
50
|
+
layout_subparsers = layout.add_subparsers(dest="build_layout_command", required=True)
|
|
51
|
+
layout_show = layout_subparsers.add_parser("show", help="读取布局摘要")
|
|
52
|
+
layout_show.add_argument("--app", required=True)
|
|
53
|
+
layout_show.set_defaults(handler=_handle_layout_show, format_hint="builder_result")
|
|
54
|
+
|
|
55
|
+
views = build_subparsers.add_parser("views", help="视图")
|
|
56
|
+
views_subparsers = views.add_subparsers(dest="build_views_command", required=True)
|
|
57
|
+
views_show = views_subparsers.add_parser("show", help="读取视图摘要")
|
|
58
|
+
views_show.add_argument("--app", required=True)
|
|
59
|
+
views_show.set_defaults(handler=_handle_views_show, format_hint="builder_result")
|
|
60
|
+
|
|
61
|
+
flow = build_subparsers.add_parser("flow", help="流程")
|
|
62
|
+
flow_subparsers = flow.add_subparsers(dest="build_flow_command", required=True)
|
|
63
|
+
flow_show = flow_subparsers.add_parser("show", help="读取流程摘要")
|
|
64
|
+
flow_show.add_argument("--app", required=True)
|
|
65
|
+
flow_show.set_defaults(handler=_handle_flow_show, format_hint="builder_result")
|
|
66
|
+
|
|
67
|
+
charts = build_subparsers.add_parser("charts", help="报表")
|
|
68
|
+
charts_subparsers = charts.add_subparsers(dest="build_charts_command", required=True)
|
|
69
|
+
charts_show = charts_subparsers.add_parser("show", help="读取报表摘要")
|
|
70
|
+
charts_show.add_argument("--app", required=True)
|
|
71
|
+
charts_show.set_defaults(handler=_handle_charts_show, format_hint="builder_result")
|
|
72
|
+
charts_apply = charts_subparsers.add_parser("apply", help="执行报表变更")
|
|
73
|
+
charts_apply.add_argument("--app", required=True)
|
|
74
|
+
add_file_arg(charts_apply, required=False, help_text="读取报表变更 JSON,支持 upsert_charts/remove_chart_ids/reorder_chart_ids")
|
|
75
|
+
add_stdin_json_flag(charts_apply)
|
|
76
|
+
charts_apply.set_defaults(handler=_handle_charts_apply, format_hint="builder_result")
|
|
77
|
+
|
|
78
|
+
portal = build_subparsers.add_parser("portal", help="门户")
|
|
79
|
+
portal_subparsers = portal.add_subparsers(dest="build_portal_command", required=True)
|
|
80
|
+
portal_show = portal_subparsers.add_parser("show", help="读取门户摘要")
|
|
81
|
+
portal_show.add_argument("--dash", required=True)
|
|
82
|
+
portal_show.add_argument("--draft", action=argparse.BooleanOptionalAction, default=True)
|
|
83
|
+
portal_show.set_defaults(handler=_handle_portal_show, format_hint="builder_result")
|
|
84
|
+
portal_apply = portal_subparsers.add_parser("apply", help="执行门户变更")
|
|
85
|
+
portal_apply.add_argument("--dash", default="")
|
|
86
|
+
portal_apply.add_argument("--name", dest="dash_name", default="")
|
|
50
87
|
portal_apply.add_argument("--package-tag-id", type=int)
|
|
51
88
|
portal_apply.add_argument("--publish", action=argparse.BooleanOptionalAction, default=True)
|
|
52
|
-
portal_apply
|
|
53
|
-
portal_apply
|
|
54
|
-
portal_apply.
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
schema_apply_subparsers = schema_apply.add_subparsers(dest="builder_schema_command", required=True)
|
|
63
|
-
schema_apply_apply = schema_apply_subparsers.add_parser("apply", help="执行字段变更")
|
|
64
|
-
schema_apply_apply.add_argument("--app-key", default="")
|
|
65
|
-
schema_apply_apply.add_argument("--package-tag-id", type=int)
|
|
66
|
-
schema_apply_apply.add_argument("--app-name", default="")
|
|
67
|
-
schema_apply_apply.add_argument("--app-title", default="")
|
|
68
|
-
schema_apply_apply.add_argument("--create-if-missing", action="store_true")
|
|
69
|
-
schema_apply_apply.add_argument("--publish", action=argparse.BooleanOptionalAction, default=True)
|
|
70
|
-
schema_apply_apply.add_argument("--add-fields-file")
|
|
71
|
-
schema_apply_apply.add_argument("--update-fields-file")
|
|
72
|
-
schema_apply_apply.add_argument("--remove-fields-file")
|
|
73
|
-
schema_apply_apply.set_defaults(handler=_handle_schema_apply, format_hint="builder_summary")
|
|
74
|
-
|
|
75
|
-
charts_apply = builder_subparsers.add_parser("charts", help="报表")
|
|
76
|
-
charts_apply_subparsers = charts_apply.add_subparsers(dest="builder_charts_command", required=True)
|
|
77
|
-
charts_apply_apply = charts_apply_subparsers.add_parser("apply", help="执行报表变更")
|
|
78
|
-
charts_apply_apply.add_argument("--app-key", required=True)
|
|
79
|
-
charts_apply_apply.add_argument("--upsert-file")
|
|
80
|
-
charts_apply_apply.add_argument("--remove-chart-ids-file")
|
|
81
|
-
charts_apply_apply.add_argument("--reorder-chart-ids-file")
|
|
82
|
-
charts_apply_apply.set_defaults(handler=_handle_charts_apply, format_hint="builder_summary")
|
|
83
|
-
|
|
84
|
-
publish_verify = builder_subparsers.add_parser("publish", help="发布校验")
|
|
85
|
-
publish_verify_subparsers = publish_verify.add_subparsers(dest="builder_publish_command", required=True)
|
|
86
|
-
publish_verify_verify = publish_verify_subparsers.add_parser("verify", help="校验应用发布")
|
|
87
|
-
publish_verify_verify.add_argument("--app-key", required=True)
|
|
88
|
-
publish_verify_verify.add_argument("--expected-package-tag-id", type=int)
|
|
89
|
-
publish_verify_verify.set_defaults(handler=_handle_publish_verify, format_hint="builder_summary")
|
|
89
|
+
add_file_arg(portal_apply, required=True, help_text="读取门户变更 JSON,至少包含 sections")
|
|
90
|
+
add_stdin_json_flag(portal_apply)
|
|
91
|
+
portal_apply.set_defaults(handler=_handle_portal_apply, format_hint="builder_result")
|
|
92
|
+
|
|
93
|
+
publish = build_subparsers.add_parser("publish", help="发布校验")
|
|
94
|
+
publish_subparsers = publish.add_subparsers(dest="build_publish_command", required=True)
|
|
95
|
+
publish_verify = publish_subparsers.add_parser("verify", help="校验应用发布")
|
|
96
|
+
publish_verify.add_argument("--app", required=True)
|
|
97
|
+
publish_verify.add_argument("--expected-package-tag-id", type=int)
|
|
98
|
+
publish_verify.set_defaults(handler=_handle_publish_verify, format_hint="builder_result")
|
|
90
99
|
|
|
91
100
|
|
|
92
101
|
def _handle_package_resolve(args: argparse.Namespace, context: CliContext) -> dict:
|
|
93
|
-
return context.
|
|
102
|
+
return context.build.package_resolve(profile=args.profile, package_name=args.name)
|
|
94
103
|
|
|
95
104
|
|
|
96
105
|
def _handle_app_resolve(args: argparse.Namespace, context: CliContext) -> dict:
|
|
97
|
-
return context.
|
|
106
|
+
return context.build.app_resolve(
|
|
98
107
|
profile=args.profile,
|
|
99
108
|
app_key=args.app_key,
|
|
100
|
-
app_name=args.
|
|
109
|
+
app_name=args.name,
|
|
101
110
|
package_tag_id=args.package_tag_id,
|
|
102
111
|
)
|
|
103
112
|
|
|
104
113
|
|
|
105
|
-
def
|
|
106
|
-
return context.
|
|
114
|
+
def _handle_app_show(args: argparse.Namespace, context: CliContext) -> dict:
|
|
115
|
+
return context.build.app_show(profile=args.profile, app_key=args.app)
|
|
107
116
|
|
|
108
117
|
|
|
109
|
-
def
|
|
110
|
-
return context.
|
|
118
|
+
def _handle_fields_show(args: argparse.Namespace, context: CliContext) -> dict:
|
|
119
|
+
return context.build.fields_show(profile=args.profile, app_key=args.app)
|
|
111
120
|
|
|
112
121
|
|
|
113
|
-
def
|
|
114
|
-
|
|
122
|
+
def _handle_fields_apply(args: argparse.Namespace, context: CliContext) -> dict:
|
|
123
|
+
payload = load_object_input(args, required=False)
|
|
124
|
+
return context.build.fields_apply(
|
|
125
|
+
profile=args.profile,
|
|
126
|
+
app_key=args.app,
|
|
127
|
+
package_tag_id=args.package_tag_id,
|
|
128
|
+
app_name=args.app_name,
|
|
129
|
+
app_title=args.app_title,
|
|
130
|
+
create_if_missing=bool(args.create_if_missing),
|
|
131
|
+
publish=bool(args.publish),
|
|
132
|
+
add_fields=_coerce_list(payload.get("add_fields")),
|
|
133
|
+
update_fields=_coerce_list(payload.get("update_fields")),
|
|
134
|
+
remove_fields=_coerce_list(payload.get("remove_fields")),
|
|
135
|
+
)
|
|
115
136
|
|
|
116
137
|
|
|
117
|
-
def
|
|
118
|
-
return context.
|
|
138
|
+
def _handle_layout_show(args: argparse.Namespace, context: CliContext) -> dict:
|
|
139
|
+
return context.build.layout_show(profile=args.profile, app_key=args.app)
|
|
119
140
|
|
|
120
141
|
|
|
121
|
-
def
|
|
122
|
-
return context.
|
|
142
|
+
def _handle_views_show(args: argparse.Namespace, context: CliContext) -> dict:
|
|
143
|
+
return context.build.views_show(profile=args.profile, app_key=args.app)
|
|
123
144
|
|
|
124
145
|
|
|
125
|
-
def
|
|
126
|
-
return context.
|
|
146
|
+
def _handle_flow_show(args: argparse.Namespace, context: CliContext) -> dict:
|
|
147
|
+
return context.build.flow_show(profile=args.profile, app_key=args.app)
|
|
127
148
|
|
|
128
149
|
|
|
129
|
-
def
|
|
130
|
-
return context.
|
|
131
|
-
profile=args.profile,
|
|
132
|
-
dash_key=args.dash_key,
|
|
133
|
-
being_draft=bool(args.being_draft),
|
|
134
|
-
)
|
|
150
|
+
def _handle_charts_show(args: argparse.Namespace, context: CliContext) -> dict:
|
|
151
|
+
return context.build.charts_show(profile=args.profile, app_key=args.app)
|
|
135
152
|
|
|
136
153
|
|
|
137
|
-
def
|
|
138
|
-
|
|
154
|
+
def _handle_charts_apply(args: argparse.Namespace, context: CliContext) -> dict:
|
|
155
|
+
payload = load_object_input(args, required=False)
|
|
156
|
+
return context.build.charts_apply(
|
|
139
157
|
profile=args.profile,
|
|
140
|
-
app_key=args.
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
create_if_missing=bool(args.create_if_missing),
|
|
145
|
-
publish=bool(args.publish),
|
|
146
|
-
add_fields=load_list_arg(args.add_fields_file, option_name="--add-fields-file"),
|
|
147
|
-
update_fields=load_list_arg(args.update_fields_file, option_name="--update-fields-file"),
|
|
148
|
-
remove_fields=load_list_arg(args.remove_fields_file, option_name="--remove-fields-file"),
|
|
158
|
+
app_key=args.app,
|
|
159
|
+
upsert_charts=_coerce_list(payload.get("upsert_charts")),
|
|
160
|
+
remove_chart_ids=_coerce_list(payload.get("remove_chart_ids")),
|
|
161
|
+
reorder_chart_ids=_coerce_list(payload.get("reorder_chart_ids")),
|
|
149
162
|
)
|
|
150
163
|
|
|
151
164
|
|
|
152
|
-
def
|
|
153
|
-
return context.
|
|
154
|
-
profile=args.profile,
|
|
155
|
-
app_key=args.app_key,
|
|
156
|
-
upsert_charts=load_list_arg(args.upsert_file, option_name="--upsert-file"),
|
|
157
|
-
remove_chart_ids=load_list_arg(args.remove_chart_ids_file, option_name="--remove-chart-ids-file"),
|
|
158
|
-
reorder_chart_ids=load_list_arg(args.reorder_chart_ids_file, option_name="--reorder-chart-ids-file"),
|
|
159
|
-
)
|
|
165
|
+
def _handle_portal_show(args: argparse.Namespace, context: CliContext) -> dict:
|
|
166
|
+
return context.build.portal_show(profile=args.profile, dash_key=args.dash, being_draft=bool(args.draft))
|
|
160
167
|
|
|
161
168
|
|
|
162
169
|
def _handle_portal_apply(args: argparse.Namespace, context: CliContext) -> dict:
|
|
163
|
-
|
|
170
|
+
payload = load_object_input(args, required=True)
|
|
171
|
+
sections = payload.get("sections")
|
|
172
|
+
if not isinstance(sections, list) or not sections:
|
|
173
|
+
raise QingflowApiError.config_error("sections must be a non-empty list")
|
|
174
|
+
return context.build.portal_apply(
|
|
164
175
|
profile=args.profile,
|
|
165
|
-
dash_key=args.
|
|
176
|
+
dash_key=args.dash,
|
|
166
177
|
dash_name=args.dash_name,
|
|
167
178
|
package_tag_id=args.package_tag_id,
|
|
168
179
|
publish=bool(args.publish),
|
|
169
|
-
sections=
|
|
170
|
-
auth=
|
|
171
|
-
icon=
|
|
172
|
-
color=
|
|
173
|
-
hide_copyright=
|
|
174
|
-
dash_global_config=
|
|
175
|
-
config=
|
|
180
|
+
sections=sections,
|
|
181
|
+
auth=payload.get("auth") if isinstance(payload.get("auth"), dict) else None,
|
|
182
|
+
icon=payload.get("icon") if isinstance(payload.get("icon"), str) else None,
|
|
183
|
+
color=payload.get("color") if isinstance(payload.get("color"), str) else None,
|
|
184
|
+
hide_copyright=payload.get("hide_copyright") if isinstance(payload.get("hide_copyright"), bool) else None,
|
|
185
|
+
dash_global_config=payload.get("dash_global_config") if isinstance(payload.get("dash_global_config"), dict) else None,
|
|
186
|
+
config=payload.get("config") if isinstance(payload.get("config"), dict) else None,
|
|
176
187
|
)
|
|
177
188
|
|
|
178
189
|
|
|
179
190
|
def _handle_publish_verify(args: argparse.Namespace, context: CliContext) -> dict:
|
|
180
|
-
return context.
|
|
191
|
+
return context.build.publish_verify(
|
|
181
192
|
profile=args.profile,
|
|
182
|
-
app_key=args.
|
|
193
|
+
app_key=args.app,
|
|
183
194
|
expected_package_tag_id=args.expected_package_tag_id,
|
|
184
195
|
)
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
def _coerce_list(value: Any) -> list[Any]:
|
|
199
|
+
return value if isinstance(value, list) else []
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import argparse
|
|
4
|
+
import json
|
|
4
5
|
import sys
|
|
5
6
|
from typing import Any
|
|
6
7
|
|
|
7
8
|
from ...errors import QingflowApiError
|
|
8
|
-
from ..json_io import load_json_list, load_json_object
|
|
9
|
+
from ..json_io import load_json_list, load_json_object
|
|
9
10
|
|
|
10
11
|
|
|
11
12
|
def parse_bool_text(value: str) -> bool:
|
|
@@ -17,26 +18,6 @@ def parse_bool_text(value: str) -> bool:
|
|
|
17
18
|
raise argparse.ArgumentTypeError("expected one of: true, false, 1, 0, yes, no")
|
|
18
19
|
|
|
19
20
|
|
|
20
|
-
def load_list_arg(path: str | None, *, option_name: str) -> list[Any]:
|
|
21
|
-
return load_optional_json_list(path, option_name=option_name)
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
def load_object_arg(path: str | None, *, option_name: str) -> dict[str, Any] | None:
|
|
25
|
-
return load_optional_json_object(path, option_name=option_name)
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
def require_list_arg(path: str | None, *, option_name: str) -> list[Any]:
|
|
29
|
-
if not path:
|
|
30
|
-
raise QingflowApiError.config_error(f"{option_name} is required")
|
|
31
|
-
return load_json_list(path, option_name=option_name)
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
def require_object_arg(path: str | None, *, option_name: str) -> dict[str, Any]:
|
|
35
|
-
if not path:
|
|
36
|
-
raise QingflowApiError.config_error(f"{option_name} is required")
|
|
37
|
-
return load_json_object(path, option_name=option_name)
|
|
38
|
-
|
|
39
|
-
|
|
40
21
|
def read_secret_arg(value: str | None, *, stdin_enabled: bool, label: str) -> str:
|
|
41
22
|
if stdin_enabled:
|
|
42
23
|
secret = sys.stdin.read().strip()
|
|
@@ -45,3 +26,96 @@ def read_secret_arg(value: str | None, *, stdin_enabled: bool, label: str) -> st
|
|
|
45
26
|
if value:
|
|
46
27
|
return value
|
|
47
28
|
raise QingflowApiError.config_error(f"{label} is required")
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def add_stdin_json_flag(parser: argparse.ArgumentParser) -> None:
|
|
32
|
+
parser.add_argument("--stdin-json", action="store_true", help="从标准输入读取 JSON")
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def add_file_arg(parser: argparse.ArgumentParser, *, required: bool = False, help_text: str = "从文件读取 JSON") -> None:
|
|
36
|
+
parser.add_argument("--file", required=required, help=help_text)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def add_field_args(parser: argparse.ArgumentParser) -> None:
|
|
40
|
+
parser.add_argument("--field", dest="fields", action="append", default=[], help="字段赋值,格式:字段名=值")
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def load_object_input(args: argparse.Namespace, *, required: bool = False) -> dict[str, Any]:
|
|
44
|
+
if getattr(args, "stdin_json", False):
|
|
45
|
+
payload = _read_stdin_json()
|
|
46
|
+
if not isinstance(payload, dict):
|
|
47
|
+
raise QingflowApiError.config_error("stdin JSON must be an object")
|
|
48
|
+
return payload
|
|
49
|
+
file_path = getattr(args, "file", None)
|
|
50
|
+
if file_path:
|
|
51
|
+
return load_json_object(file_path, option_name="--file")
|
|
52
|
+
if required:
|
|
53
|
+
raise QingflowApiError.config_error("either --stdin-json or --file is required")
|
|
54
|
+
return {}
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def load_list_input(args: argparse.Namespace, *, required: bool = False) -> list[Any]:
|
|
58
|
+
if getattr(args, "stdin_json", False):
|
|
59
|
+
payload = _read_stdin_json()
|
|
60
|
+
if not isinstance(payload, list):
|
|
61
|
+
raise QingflowApiError.config_error("stdin JSON must be a list")
|
|
62
|
+
return payload
|
|
63
|
+
file_path = getattr(args, "file", None)
|
|
64
|
+
if file_path:
|
|
65
|
+
return load_json_list(file_path, option_name="--file")
|
|
66
|
+
if required:
|
|
67
|
+
raise QingflowApiError.config_error("either --stdin-json or --file is required")
|
|
68
|
+
return []
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def parse_field_assignments(items: list[str]) -> dict[str, Any]:
|
|
72
|
+
payload: dict[str, Any] = {}
|
|
73
|
+
for item in items:
|
|
74
|
+
if "=" not in item:
|
|
75
|
+
raise QingflowApiError.config_error("field assignments must use name=value format")
|
|
76
|
+
key, raw_value = item.split("=", 1)
|
|
77
|
+
name = key.strip()
|
|
78
|
+
if not name:
|
|
79
|
+
raise QingflowApiError.config_error("field name cannot be empty")
|
|
80
|
+
payload[name] = _coerce_value(raw_value)
|
|
81
|
+
return payload
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def merge_object_inputs(args: argparse.Namespace, *, allow_empty: bool = False) -> dict[str, Any]:
|
|
85
|
+
payload = parse_field_assignments(list(getattr(args, "fields", []) or []))
|
|
86
|
+
if getattr(args, "stdin_json", False) or getattr(args, "file", None):
|
|
87
|
+
loaded = load_object_input(args, required=False)
|
|
88
|
+
payload.update(loaded)
|
|
89
|
+
if not payload and not allow_empty:
|
|
90
|
+
raise QingflowApiError.config_error("provide at least one --field or pass --stdin-json/--file")
|
|
91
|
+
return payload
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def _read_stdin_json() -> Any:
|
|
95
|
+
raw = sys.stdin.read()
|
|
96
|
+
if not raw.strip():
|
|
97
|
+
raise QingflowApiError.config_error("stdin JSON is empty")
|
|
98
|
+
try:
|
|
99
|
+
return json.loads(raw)
|
|
100
|
+
except json.JSONDecodeError as error:
|
|
101
|
+
raise QingflowApiError.config_error(f"stdin JSON is invalid: {error.msg}") from error
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def _coerce_value(raw: str) -> Any:
|
|
105
|
+
text = raw.strip()
|
|
106
|
+
if not text:
|
|
107
|
+
return ""
|
|
108
|
+
if text.startswith("{") or text.startswith("[") or text.startswith('"'):
|
|
109
|
+
try:
|
|
110
|
+
return json.loads(text)
|
|
111
|
+
except json.JSONDecodeError:
|
|
112
|
+
return raw
|
|
113
|
+
normalized = text.lower()
|
|
114
|
+
if normalized in {"true", "false", "null"}:
|
|
115
|
+
return json.loads(normalized)
|
|
116
|
+
try:
|
|
117
|
+
if "." in text:
|
|
118
|
+
return float(text)
|
|
119
|
+
return int(text)
|
|
120
|
+
except ValueError:
|
|
121
|
+
return raw
|