mofox-plugin-dev-toolkit 0.5.0__tar.gz → 0.5.1__tar.gz
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.
- {mofox_plugin_dev_toolkit-0.5.0/mofox_plugin_dev_toolkit.egg-info → mofox_plugin_dev_toolkit-0.5.1}/PKG-INFO +1 -1
- {mofox_plugin_dev_toolkit-0.5.0 → mofox_plugin_dev_toolkit-0.5.1/mofox_plugin_dev_toolkit.egg-info}/PKG-INFO +1 -1
- {mofox_plugin_dev_toolkit-0.5.0 → mofox_plugin_dev_toolkit-0.5.1}/mofox_plugin_dev_toolkit.egg-info/SOURCES.txt +1 -0
- {mofox_plugin_dev_toolkit-0.5.0 → mofox_plugin_dev_toolkit-0.5.1}/mpdt/commands/build.py +17 -4
- {mofox_plugin_dev_toolkit-0.5.0 → mofox_plugin_dev_toolkit-0.5.1}/mpdt/commands/check.py +14 -0
- {mofox_plugin_dev_toolkit-0.5.0 → mofox_plugin_dev_toolkit-0.5.1}/mpdt/commands/init.py +30 -2
- {mofox_plugin_dev_toolkit-0.5.0 → mofox_plugin_dev_toolkit-0.5.1}/mpdt/commands/market.py +14 -5
- mofox_plugin_dev_toolkit-0.5.1/mpdt/utils/manifest_metadata.py +199 -0
- {mofox_plugin_dev_toolkit-0.5.0 → mofox_plugin_dev_toolkit-0.5.1}/mpdt/validators/metadata_validator.py +8 -0
- {mofox_plugin_dev_toolkit-0.5.0 → mofox_plugin_dev_toolkit-0.5.1}/pyproject.toml +1 -1
- {mofox_plugin_dev_toolkit-0.5.0 → mofox_plugin_dev_toolkit-0.5.1}/LICENSE +0 -0
- {mofox_plugin_dev_toolkit-0.5.0 → mofox_plugin_dev_toolkit-0.5.1}/MANIFEST.in +0 -0
- {mofox_plugin_dev_toolkit-0.5.0 → mofox_plugin_dev_toolkit-0.5.1}/README.md +0 -0
- {mofox_plugin_dev_toolkit-0.5.0 → mofox_plugin_dev_toolkit-0.5.1}/mofox_plugin_dev_toolkit.egg-info/dependency_links.txt +0 -0
- {mofox_plugin_dev_toolkit-0.5.0 → mofox_plugin_dev_toolkit-0.5.1}/mofox_plugin_dev_toolkit.egg-info/entry_points.txt +0 -0
- {mofox_plugin_dev_toolkit-0.5.0 → mofox_plugin_dev_toolkit-0.5.1}/mofox_plugin_dev_toolkit.egg-info/requires.txt +0 -0
- {mofox_plugin_dev_toolkit-0.5.0 → mofox_plugin_dev_toolkit-0.5.1}/mofox_plugin_dev_toolkit.egg-info/top_level.txt +0 -0
- {mofox_plugin_dev_toolkit-0.5.0 → mofox_plugin_dev_toolkit-0.5.1}/mpdt/__init__.py +0 -0
- {mofox_plugin_dev_toolkit-0.5.0 → mofox_plugin_dev_toolkit-0.5.1}/mpdt/__main__.py +0 -0
- {mofox_plugin_dev_toolkit-0.5.0 → mofox_plugin_dev_toolkit-0.5.1}/mpdt/cli.py +0 -0
- {mofox_plugin_dev_toolkit-0.5.0 → mofox_plugin_dev_toolkit-0.5.1}/mpdt/commands/__init__.py +0 -0
- {mofox_plugin_dev_toolkit-0.5.0 → mofox_plugin_dev_toolkit-0.5.1}/mpdt/commands/dev.py +0 -0
- {mofox_plugin_dev_toolkit-0.5.0 → mofox_plugin_dev_toolkit-0.5.1}/mpdt/commands/generate.py +0 -0
- {mofox_plugin_dev_toolkit-0.5.0 → mofox_plugin_dev_toolkit-0.5.1}/mpdt/dev/bridge_plugin/__init__.py +0 -0
- {mofox_plugin_dev_toolkit-0.5.0 → mofox_plugin_dev_toolkit-0.5.1}/mpdt/dev/bridge_plugin/cleanup_handler.py +0 -0
- {mofox_plugin_dev_toolkit-0.5.0 → mofox_plugin_dev_toolkit-0.5.1}/mpdt/dev/bridge_plugin/dev_config.py +0 -0
- {mofox_plugin_dev_toolkit-0.5.0 → mofox_plugin_dev_toolkit-0.5.1}/mpdt/dev/bridge_plugin/file_watcher.py +0 -0
- {mofox_plugin_dev_toolkit-0.5.0 → mofox_plugin_dev_toolkit-0.5.1}/mpdt/dev/bridge_plugin/manifest.json +0 -0
- {mofox_plugin_dev_toolkit-0.5.0 → mofox_plugin_dev_toolkit-0.5.1}/mpdt/dev/bridge_plugin/plugin.py +0 -0
- {mofox_plugin_dev_toolkit-0.5.0 → mofox_plugin_dev_toolkit-0.5.1}/mpdt/market/__init__.py +0 -0
- {mofox_plugin_dev_toolkit-0.5.0 → mofox_plugin_dev_toolkit-0.5.1}/mpdt/market/client.py +0 -0
- {mofox_plugin_dev_toolkit-0.5.0 → mofox_plugin_dev_toolkit-0.5.1}/mpdt/market/config.py +0 -0
- {mofox_plugin_dev_toolkit-0.5.0 → mofox_plugin_dev_toolkit-0.5.1}/mpdt/market/git.py +0 -0
- {mofox_plugin_dev_toolkit-0.5.0 → mofox_plugin_dev_toolkit-0.5.1}/mpdt/market/github.py +0 -0
- {mofox_plugin_dev_toolkit-0.5.0 → mofox_plugin_dev_toolkit-0.5.1}/mpdt/market/manifest.py +0 -0
- {mofox_plugin_dev_toolkit-0.5.0 → mofox_plugin_dev_toolkit-0.5.1}/mpdt/templates/__init__.py +0 -0
- {mofox_plugin_dev_toolkit-0.5.0 → mofox_plugin_dev_toolkit-0.5.1}/mpdt/templates/action_template.py +0 -0
- {mofox_plugin_dev_toolkit-0.5.0 → mofox_plugin_dev_toolkit-0.5.1}/mpdt/templates/adapter_template.py +0 -0
- {mofox_plugin_dev_toolkit-0.5.0 → mofox_plugin_dev_toolkit-0.5.1}/mpdt/templates/chatter_template.py +0 -0
- {mofox_plugin_dev_toolkit-0.5.0 → mofox_plugin_dev_toolkit-0.5.1}/mpdt/templates/collection_template.py +0 -0
- {mofox_plugin_dev_toolkit-0.5.0 → mofox_plugin_dev_toolkit-0.5.1}/mpdt/templates/config_template.py +0 -0
- {mofox_plugin_dev_toolkit-0.5.0 → mofox_plugin_dev_toolkit-0.5.1}/mpdt/templates/event_template.py +0 -0
- {mofox_plugin_dev_toolkit-0.5.0 → mofox_plugin_dev_toolkit-0.5.1}/mpdt/templates/plus_command_template.py +0 -0
- {mofox_plugin_dev_toolkit-0.5.0 → mofox_plugin_dev_toolkit-0.5.1}/mpdt/templates/prompt_template.py +0 -0
- {mofox_plugin_dev_toolkit-0.5.0 → mofox_plugin_dev_toolkit-0.5.1}/mpdt/templates/router_template.py +0 -0
- {mofox_plugin_dev_toolkit-0.5.0 → mofox_plugin_dev_toolkit-0.5.1}/mpdt/templates/service_template.py +0 -0
- {mofox_plugin_dev_toolkit-0.5.0 → mofox_plugin_dev_toolkit-0.5.1}/mpdt/templates/tool_template.py +0 -0
- {mofox_plugin_dev_toolkit-0.5.0 → mofox_plugin_dev_toolkit-0.5.1}/mpdt/utils/__init__.py +0 -0
- {mofox_plugin_dev_toolkit-0.5.0 → mofox_plugin_dev_toolkit-0.5.1}/mpdt/utils/code_parser.py +0 -0
- {mofox_plugin_dev_toolkit-0.5.0 → mofox_plugin_dev_toolkit-0.5.1}/mpdt/utils/color_printer.py +0 -0
- {mofox_plugin_dev_toolkit-0.5.0 → mofox_plugin_dev_toolkit-0.5.1}/mpdt/utils/config_loader.py +0 -0
- {mofox_plugin_dev_toolkit-0.5.0 → mofox_plugin_dev_toolkit-0.5.1}/mpdt/utils/config_manager.py +0 -0
- {mofox_plugin_dev_toolkit-0.5.0 → mofox_plugin_dev_toolkit-0.5.1}/mpdt/utils/file_ops.py +0 -0
- {mofox_plugin_dev_toolkit-0.5.0 → mofox_plugin_dev_toolkit-0.5.1}/mpdt/utils/license_generator.py +0 -0
- {mofox_plugin_dev_toolkit-0.5.0 → mofox_plugin_dev_toolkit-0.5.1}/mpdt/utils/plugin_parser.py +0 -0
- {mofox_plugin_dev_toolkit-0.5.0 → mofox_plugin_dev_toolkit-0.5.1}/mpdt/utils/template_engine.py +0 -0
- {mofox_plugin_dev_toolkit-0.5.0 → mofox_plugin_dev_toolkit-0.5.1}/mpdt/validators/__init__.py +0 -0
- {mofox_plugin_dev_toolkit-0.5.0 → mofox_plugin_dev_toolkit-0.5.1}/mpdt/validators/auto_fix_validator.py +0 -0
- {mofox_plugin_dev_toolkit-0.5.0 → mofox_plugin_dev_toolkit-0.5.1}/mpdt/validators/base.py +0 -0
- {mofox_plugin_dev_toolkit-0.5.0 → mofox_plugin_dev_toolkit-0.5.1}/mpdt/validators/component_validator.py +0 -0
- {mofox_plugin_dev_toolkit-0.5.0 → mofox_plugin_dev_toolkit-0.5.1}/mpdt/validators/config_validator.py +0 -0
- {mofox_plugin_dev_toolkit-0.5.0 → mofox_plugin_dev_toolkit-0.5.1}/mpdt/validators/structure_validator.py +0 -0
- {mofox_plugin_dev_toolkit-0.5.0 → mofox_plugin_dev_toolkit-0.5.1}/mpdt/validators/style_validator.py +0 -0
- {mofox_plugin_dev_toolkit-0.5.0 → mofox_plugin_dev_toolkit-0.5.1}/mpdt/validators/type_validator.py +0 -0
- {mofox_plugin_dev_toolkit-0.5.0 → mofox_plugin_dev_toolkit-0.5.1}/setup.cfg +0 -0
|
@@ -16,6 +16,7 @@ from __future__ import annotations
|
|
|
16
16
|
|
|
17
17
|
import json
|
|
18
18
|
import re
|
|
19
|
+
import sys
|
|
19
20
|
import zipfile
|
|
20
21
|
from dataclasses import dataclass
|
|
21
22
|
from hashlib import sha256
|
|
@@ -31,6 +32,7 @@ from mpdt.utils.color_printer import (
|
|
|
31
32
|
print_success,
|
|
32
33
|
print_warning,
|
|
33
34
|
)
|
|
35
|
+
from mpdt.utils.manifest_metadata import ensure_manifest_metadata_interactive, metadata_errors
|
|
34
36
|
|
|
35
37
|
# 构建时默认排除的文件/目录名称(精确匹配)
|
|
36
38
|
_EXCLUDE_NAMES: set[str] = {
|
|
@@ -179,6 +181,18 @@ def _save_manifest(plugin_dir: Path, manifest: dict) -> None:
|
|
|
179
181
|
json.dump(manifest, f, ensure_ascii=False, indent=4)
|
|
180
182
|
|
|
181
183
|
|
|
184
|
+
def _validate_manifest_metadata(manifest: dict) -> None:
|
|
185
|
+
"""验证打包所需的 manifest 元数据。"""
|
|
186
|
+
required = ["name", "version", "description", "author", "entry_point"]
|
|
187
|
+
for field in required:
|
|
188
|
+
if field not in manifest:
|
|
189
|
+
raise ValueError(f"manifest.json 缺少必需字段: '{field}'")
|
|
190
|
+
|
|
191
|
+
errors = metadata_errors(manifest)
|
|
192
|
+
if errors:
|
|
193
|
+
raise ValueError(errors[0])
|
|
194
|
+
|
|
195
|
+
|
|
182
196
|
def build_plugin(
|
|
183
197
|
plugin_path: str = ".",
|
|
184
198
|
output_dir: str = "dist",
|
|
@@ -263,10 +277,9 @@ def build_package(
|
|
|
263
277
|
if manifest is None:
|
|
264
278
|
return
|
|
265
279
|
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
raise ValueError(f"manifest.json 缺少必需字段: '{field}'")
|
|
280
|
+
manifest = ensure_manifest_metadata_interactive(plugin_dir, manifest)
|
|
281
|
+
|
|
282
|
+
_validate_manifest_metadata(manifest)
|
|
270
283
|
|
|
271
284
|
plugin_name: str = manifest["name"]
|
|
272
285
|
plugin_version: str = manifest["version"]
|
|
@@ -2,12 +2,15 @@
|
|
|
2
2
|
静态检查命令实现
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
+
import json
|
|
6
|
+
|
|
5
7
|
from pathlib import Path
|
|
6
8
|
|
|
7
9
|
from rich.panel import Panel
|
|
8
10
|
from rich.table import Table
|
|
9
11
|
|
|
10
12
|
from mpdt.utils.color_printer import console, print_error, print_info, print_success, print_warning
|
|
13
|
+
from mpdt.utils.manifest_metadata import ensure_manifest_metadata_interactive
|
|
11
14
|
from mpdt.validators import (
|
|
12
15
|
AutoFixValidator,
|
|
13
16
|
ComponentValidator,
|
|
@@ -77,6 +80,17 @@ def check_plugin(
|
|
|
77
80
|
# 元数据验证
|
|
78
81
|
if not skip_metadata:
|
|
79
82
|
print_info("正在检查插件元数据...")
|
|
83
|
+
manifest_path = path / "manifest.json"
|
|
84
|
+
if manifest_path.exists():
|
|
85
|
+
try:
|
|
86
|
+
with open(manifest_path, encoding="utf-8") as f:
|
|
87
|
+
manifest_data = json.load(f)
|
|
88
|
+
ensure_manifest_metadata_interactive(path, manifest_data)
|
|
89
|
+
except json.JSONDecodeError:
|
|
90
|
+
pass
|
|
91
|
+
except ValueError as e:
|
|
92
|
+
print_error(str(e))
|
|
93
|
+
return
|
|
80
94
|
validator = MetadataValidator(path)
|
|
81
95
|
result = validator.validate()
|
|
82
96
|
all_results.append(result)
|
|
@@ -17,6 +17,7 @@ from typing import Any
|
|
|
17
17
|
|
|
18
18
|
import questionary
|
|
19
19
|
|
|
20
|
+
from mpdt.utils.manifest_metadata import prompt_manifest_metadata
|
|
20
21
|
from mpdt.utils.color_printer import (
|
|
21
22
|
console,
|
|
22
23
|
print_error,
|
|
@@ -64,12 +65,20 @@ def init_plugin(
|
|
|
64
65
|
if not plugin_name:
|
|
65
66
|
plugin_info = _interactive_init()
|
|
66
67
|
plugin_name = plugin_info["plugin_name"]
|
|
68
|
+
description = plugin_info.get("description", "")
|
|
67
69
|
template = plugin_info["template"]
|
|
68
70
|
author = plugin_info.get("author")
|
|
69
71
|
email = plugin_info.get("email")
|
|
70
72
|
license_type = plugin_info["license"]
|
|
71
73
|
with_docs = plugin_info.get("with_docs", False)
|
|
72
74
|
init_git = plugin_info.get("init_git", False)
|
|
75
|
+
categories = plugin_info["categories"]
|
|
76
|
+
tags = plugin_info["tags"]
|
|
77
|
+
else:
|
|
78
|
+
description = ""
|
|
79
|
+
metadata = prompt_manifest_metadata()
|
|
80
|
+
categories = metadata["categories"]
|
|
81
|
+
tags = metadata["tags"]
|
|
73
82
|
|
|
74
83
|
# 此时 plugin_name 必定不为 None
|
|
75
84
|
assert plugin_name is not None
|
|
@@ -96,11 +105,14 @@ def init_plugin(
|
|
|
96
105
|
_create_plugin_structure(
|
|
97
106
|
plugin_dir=plugin_dir,
|
|
98
107
|
plugin_name=plugin_name,
|
|
108
|
+
description=description,
|
|
99
109
|
template=template,
|
|
100
110
|
author=author,
|
|
101
111
|
email=email,
|
|
102
112
|
license_type=license_type,
|
|
103
113
|
with_docs=with_docs,
|
|
114
|
+
categories=categories,
|
|
115
|
+
tags=tags,
|
|
104
116
|
verbose=verbose,
|
|
105
117
|
)
|
|
106
118
|
|
|
@@ -194,6 +206,10 @@ def _interactive_init() -> dict[str, Any]:
|
|
|
194
206
|
),
|
|
195
207
|
).ask()
|
|
196
208
|
|
|
209
|
+
metadata = prompt_manifest_metadata()
|
|
210
|
+
answers["categories"] = metadata["categories"]
|
|
211
|
+
answers["tags"] = metadata["tags"]
|
|
212
|
+
|
|
197
213
|
return answers
|
|
198
214
|
|
|
199
215
|
# ============================================================================
|
|
@@ -204,11 +220,14 @@ def _interactive_init() -> dict[str, Any]:
|
|
|
204
220
|
def _create_plugin_structure(
|
|
205
221
|
plugin_dir: Path,
|
|
206
222
|
plugin_name: str,
|
|
223
|
+
description: str,
|
|
207
224
|
template: str,
|
|
208
225
|
author: str | None,
|
|
209
226
|
email: str | None,
|
|
210
227
|
license_type: str,
|
|
211
228
|
with_docs: bool,
|
|
229
|
+
categories: list[str],
|
|
230
|
+
tags: list[str],
|
|
212
231
|
verbose: bool,
|
|
213
232
|
) -> None:
|
|
214
233
|
"""创建插件目录结构"""
|
|
@@ -217,7 +236,7 @@ def _create_plugin_structure(
|
|
|
217
236
|
ensure_dir(plugin_dir)
|
|
218
237
|
|
|
219
238
|
# 创建 manifest.json
|
|
220
|
-
manifest_content = _generate_manifest_file(plugin_name, author, template)
|
|
239
|
+
manifest_content = _generate_manifest_file(plugin_name, author, template, description, categories, tags)
|
|
221
240
|
safe_write_file(plugin_dir / "manifest.json", manifest_content)
|
|
222
241
|
if verbose:
|
|
223
242
|
console.print("[dim]✓ 生成清单文件: manifest.json[/dim]")
|
|
@@ -281,7 +300,14 @@ def _create_plugin_structure(
|
|
|
281
300
|
console.print(f"[dim]✓ 生成许可证文件: {license_type}[/dim]")
|
|
282
301
|
|
|
283
302
|
|
|
284
|
-
def _generate_manifest_file(
|
|
303
|
+
def _generate_manifest_file(
|
|
304
|
+
plugin_name: str,
|
|
305
|
+
author: str | None,
|
|
306
|
+
template: str,
|
|
307
|
+
description: str = "",
|
|
308
|
+
categories: list[str] | None = None,
|
|
309
|
+
tags: list[str] | None = None,
|
|
310
|
+
) -> str:
|
|
285
311
|
"""生成 manifest.json 文件内容
|
|
286
312
|
|
|
287
313
|
Args:
|
|
@@ -346,6 +372,8 @@ def _generate_manifest_file(plugin_name: str, author: str | None, template: str,
|
|
|
346
372
|
"version": "1.0.0",
|
|
347
373
|
"description": description or f"{plugin_name} 插件",
|
|
348
374
|
"author": author or "Your Name",
|
|
375
|
+
"categories": categories or ["tool"],
|
|
376
|
+
"tags": tags or [plugin_name],
|
|
349
377
|
"dependencies": {"plugins": [], "components": []},
|
|
350
378
|
"include": template_components.get(template, []),
|
|
351
379
|
"entry_point": "plugin.py",
|
|
@@ -30,6 +30,7 @@ from mpdt.market.manifest import (
|
|
|
30
30
|
version_payload,
|
|
31
31
|
)
|
|
32
32
|
from mpdt.utils.color_printer import console
|
|
33
|
+
from mpdt.utils.manifest_metadata import ensure_manifest_metadata_interactive_async
|
|
33
34
|
|
|
34
35
|
|
|
35
36
|
def market_doctor(market_url: str | None = None, token: str | None = None) -> None:
|
|
@@ -49,7 +50,7 @@ def market_register(plugin_path: str = ".", market_url: str | None = None, token
|
|
|
49
50
|
"""Register a plugin in the market."""
|
|
50
51
|
|
|
51
52
|
async def run() -> None:
|
|
52
|
-
manifest =
|
|
53
|
+
manifest = await _load_manifest_for_market(plugin_path)
|
|
53
54
|
payload = plugin_payload(manifest, repository_url=repository_url)
|
|
54
55
|
result = await _client(market_url, token).register_plugin(payload)
|
|
55
56
|
_print_ok(f"Plugin registered: {result['plugin_id']} ({result['status']})")
|
|
@@ -61,7 +62,7 @@ def market_update(plugin_path: str = ".", market_url: str | None = None, token:
|
|
|
61
62
|
"""Update plugin metadata in the market."""
|
|
62
63
|
|
|
63
64
|
async def run() -> None:
|
|
64
|
-
manifest =
|
|
65
|
+
manifest = await _load_manifest_for_market(plugin_path)
|
|
65
66
|
payload = plugin_payload(manifest, repository_url=repository_url)
|
|
66
67
|
plugin_id = payload.pop("plugin_id")
|
|
67
68
|
result = await _client(market_url, token).update_plugin(plugin_id, payload)
|
|
@@ -98,7 +99,7 @@ def market_submit_version(
|
|
|
98
99
|
"""Build and submit a plugin version to the market."""
|
|
99
100
|
|
|
100
101
|
async def run() -> None:
|
|
101
|
-
manifest =
|
|
102
|
+
manifest = await _load_manifest_for_market(plugin_path)
|
|
102
103
|
package = build_package(plugin_path=plugin_path, output_dir=output_dir, with_docs=with_docs, fmt="mfp", show_progress=False)
|
|
103
104
|
if package is None:
|
|
104
105
|
return
|
|
@@ -132,7 +133,7 @@ def market_sync(
|
|
|
132
133
|
"""Rebuild and sync version metadata."""
|
|
133
134
|
|
|
134
135
|
async def run() -> None:
|
|
135
|
-
manifest =
|
|
136
|
+
manifest = await _load_manifest_for_market(plugin_path)
|
|
136
137
|
package = build_package(plugin_path=plugin_path, output_dir=output_dir, with_docs=with_docs, fmt="mfp", show_progress=False)
|
|
137
138
|
if package is None:
|
|
138
139
|
return
|
|
@@ -173,7 +174,7 @@ def market_publish(
|
|
|
173
174
|
|
|
174
175
|
async def run() -> None:
|
|
175
176
|
plugin_dir = Path(plugin_path).resolve()
|
|
176
|
-
manifest =
|
|
177
|
+
manifest = await _load_manifest_for_market(str(plugin_dir))
|
|
177
178
|
package = build_package(plugin_path=str(plugin_dir), output_dir=output_dir, with_docs=with_docs, fmt="mfp", show_progress=False)
|
|
178
179
|
if package is None:
|
|
179
180
|
return
|
|
@@ -433,6 +434,14 @@ def _run_market(awaitable) -> None:
|
|
|
433
434
|
_print_error(f"Market server connection failed: {e}")
|
|
434
435
|
|
|
435
436
|
|
|
437
|
+
async def _load_manifest_for_market(plugin_path: str) -> dict:
|
|
438
|
+
"""Load manifest and repair required market metadata in async flows before proceeding."""
|
|
439
|
+
resolved_path = Path(plugin_path).resolve()
|
|
440
|
+
manifest = load_manifest(str(resolved_path))
|
|
441
|
+
await ensure_manifest_metadata_interactive_async(resolved_path, manifest)
|
|
442
|
+
return load_manifest(str(resolved_path))
|
|
443
|
+
|
|
444
|
+
|
|
436
445
|
def _print_ok(message: str) -> None:
|
|
437
446
|
"""Print an ASCII success message."""
|
|
438
447
|
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
"""Manifest metadata rules and interactive prompts."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
import json
|
|
7
|
+
import sys
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
import questionary
|
|
12
|
+
|
|
13
|
+
from .color_printer import print_success, print_warning
|
|
14
|
+
|
|
15
|
+
ALLOWED_CATEGORIES = ("tool", "chat", "fun", "information", "moderation")
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def get_categories(manifest: dict[str, Any]) -> list[str]:
|
|
19
|
+
"""Return normalized categories from manifest."""
|
|
20
|
+
value = manifest.get("categories")
|
|
21
|
+
if not isinstance(value, list):
|
|
22
|
+
return []
|
|
23
|
+
return [item.strip() for item in value if isinstance(item, str) and item.strip()]
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def get_tags(manifest: dict[str, Any]) -> list[str]:
|
|
27
|
+
"""Return normalized tags from manifest."""
|
|
28
|
+
value = manifest.get("tags")
|
|
29
|
+
if not isinstance(value, list):
|
|
30
|
+
return []
|
|
31
|
+
normalized: list[str] = []
|
|
32
|
+
for item in value:
|
|
33
|
+
if not isinstance(item, str):
|
|
34
|
+
continue
|
|
35
|
+
tag = item.strip()
|
|
36
|
+
if tag and tag not in normalized:
|
|
37
|
+
normalized.append(tag)
|
|
38
|
+
return normalized
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def normalize_tags(raw_tags: str) -> list[str]:
|
|
42
|
+
"""Parse raw tag input into a de-duplicated tag list."""
|
|
43
|
+
tags: list[str] = []
|
|
44
|
+
normalized_input = raw_tags.replace(",", ",").replace("\n", ",")
|
|
45
|
+
for part in normalized_input.split(","):
|
|
46
|
+
tag = part.strip()
|
|
47
|
+
if tag and tag not in tags:
|
|
48
|
+
tags.append(tag)
|
|
49
|
+
return tags
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def categories_are_valid(manifest: dict[str, Any]) -> bool:
|
|
53
|
+
"""Return whether manifest categories satisfy the packaging rules."""
|
|
54
|
+
categories = get_categories(manifest)
|
|
55
|
+
return len(categories) == 1 and categories[0] in ALLOWED_CATEGORIES
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def tags_are_valid(manifest: dict[str, Any]) -> bool:
|
|
59
|
+
"""Return whether manifest tags satisfy the packaging rules."""
|
|
60
|
+
return bool(get_tags(manifest))
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def metadata_errors(manifest: dict[str, Any]) -> list[str]:
|
|
64
|
+
"""Return validation errors for required manifest metadata."""
|
|
65
|
+
errors: list[str] = []
|
|
66
|
+
if not categories_are_valid(manifest):
|
|
67
|
+
allowed = ", ".join(ALLOWED_CATEGORIES)
|
|
68
|
+
errors.append(f"manifest.json 的 categories 必须是只包含一个值的数组,且取值只能是: {allowed}")
|
|
69
|
+
if not tags_are_valid(manifest):
|
|
70
|
+
errors.append("manifest.json 的 tags 必须是至少包含一个非空字符串的数组")
|
|
71
|
+
return errors
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def _is_interactive_terminal() -> bool:
|
|
75
|
+
"""Return whether stdin/stdout are attached to a TTY."""
|
|
76
|
+
return sys.stdin.isatty() and sys.stdout.isatty()
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def _has_running_event_loop() -> bool:
|
|
80
|
+
"""Return whether the current thread is already inside a running event loop."""
|
|
81
|
+
try:
|
|
82
|
+
asyncio.get_running_loop()
|
|
83
|
+
except RuntimeError:
|
|
84
|
+
return False
|
|
85
|
+
return True
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def _finalize_manifest_metadata(manifest: dict[str, Any]) -> dict[str, Any]:
|
|
89
|
+
"""Normalize legacy single-value metadata keys."""
|
|
90
|
+
manifest.pop("category", None)
|
|
91
|
+
manifest.pop("tag", None)
|
|
92
|
+
return manifest
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def _save_manifest(plugin_dir: Path, manifest: dict[str, Any]) -> None:
|
|
96
|
+
"""Persist manifest.json after interactive metadata repair."""
|
|
97
|
+
manifest_path = plugin_dir / "manifest.json"
|
|
98
|
+
with open(manifest_path, "w", encoding="utf-8") as f:
|
|
99
|
+
json.dump(manifest, f, ensure_ascii=False, indent=4)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def prompt_manifest_metadata(existing_manifest: dict[str, Any] | None = None) -> dict[str, list[str]]:
|
|
103
|
+
"""Collect required manifest metadata from the console."""
|
|
104
|
+
manifest = existing_manifest or {}
|
|
105
|
+
existing_categories = get_categories(manifest)
|
|
106
|
+
default_category = existing_categories[0] if existing_categories else ALLOWED_CATEGORIES[0]
|
|
107
|
+
existing_tags = ", ".join(get_tags(manifest))
|
|
108
|
+
|
|
109
|
+
category = questionary.select(
|
|
110
|
+
"选择插件分类 categories(只能选一个):",
|
|
111
|
+
choices=[questionary.Choice(item, value=item) for item in ALLOWED_CATEGORIES],
|
|
112
|
+
default=default_category,
|
|
113
|
+
).ask()
|
|
114
|
+
if category is None:
|
|
115
|
+
raise ValueError("已取消填写 categories")
|
|
116
|
+
|
|
117
|
+
tags_text = questionary.text(
|
|
118
|
+
"填写插件 tags(多个标签用逗号分隔):",
|
|
119
|
+
default=existing_tags,
|
|
120
|
+
validate=lambda value: bool(normalize_tags(value)) or "至少填写一个 tag",
|
|
121
|
+
).ask()
|
|
122
|
+
if tags_text is None:
|
|
123
|
+
raise ValueError("已取消填写 tags")
|
|
124
|
+
|
|
125
|
+
return {
|
|
126
|
+
"categories": [category],
|
|
127
|
+
"tags": normalize_tags(tags_text),
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
async def prompt_manifest_metadata_async(existing_manifest: dict[str, Any] | None = None) -> dict[str, list[str]]:
|
|
132
|
+
"""Collect required manifest metadata from the console inside an event loop."""
|
|
133
|
+
manifest = existing_manifest or {}
|
|
134
|
+
existing_categories = get_categories(manifest)
|
|
135
|
+
default_category = existing_categories[0] if existing_categories else ALLOWED_CATEGORIES[0]
|
|
136
|
+
existing_tags = ", ".join(get_tags(manifest))
|
|
137
|
+
|
|
138
|
+
category = await questionary.select(
|
|
139
|
+
"选择插件分类 categories(只能选一个):",
|
|
140
|
+
choices=[questionary.Choice(item, value=item) for item in ALLOWED_CATEGORIES],
|
|
141
|
+
default=default_category,
|
|
142
|
+
).ask_async()
|
|
143
|
+
if category is None:
|
|
144
|
+
raise ValueError("已取消填写 categories")
|
|
145
|
+
|
|
146
|
+
tags_text = await questionary.text(
|
|
147
|
+
"填写插件 tags(多个标签用逗号分隔):",
|
|
148
|
+
default=existing_tags,
|
|
149
|
+
validate=lambda value: bool(normalize_tags(value)) or "至少填写一个 tag",
|
|
150
|
+
).ask_async()
|
|
151
|
+
if tags_text is None:
|
|
152
|
+
raise ValueError("已取消填写 tags")
|
|
153
|
+
|
|
154
|
+
return {
|
|
155
|
+
"categories": [category],
|
|
156
|
+
"tags": normalize_tags(tags_text),
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def ensure_manifest_metadata_interactive(plugin_dir: Path, manifest: dict[str, Any]) -> dict[str, Any]:
|
|
161
|
+
"""Prompt for missing manifest metadata and persist it when running in a TTY."""
|
|
162
|
+
errors = metadata_errors(manifest)
|
|
163
|
+
if not errors:
|
|
164
|
+
return _finalize_manifest_metadata(manifest)
|
|
165
|
+
|
|
166
|
+
if not _is_interactive_terminal() or _has_running_event_loop():
|
|
167
|
+
return manifest
|
|
168
|
+
|
|
169
|
+
print_warning("检测到 manifest.json 缺少或无效的 categories/tags,进入交互补全...")
|
|
170
|
+
for error in errors:
|
|
171
|
+
print_warning(error)
|
|
172
|
+
|
|
173
|
+
manifest.update(prompt_manifest_metadata(manifest))
|
|
174
|
+
_finalize_manifest_metadata(manifest)
|
|
175
|
+
_save_manifest(plugin_dir, manifest)
|
|
176
|
+
|
|
177
|
+
print_success("已更新 manifest.json 中的 categories 和 tags")
|
|
178
|
+
return manifest
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
async def ensure_manifest_metadata_interactive_async(plugin_dir: Path, manifest: dict[str, Any]) -> dict[str, Any]:
|
|
182
|
+
"""Async prompt for missing manifest metadata and persist it when running in a TTY."""
|
|
183
|
+
errors = metadata_errors(manifest)
|
|
184
|
+
if not errors:
|
|
185
|
+
return _finalize_manifest_metadata(manifest)
|
|
186
|
+
|
|
187
|
+
if not _is_interactive_terminal():
|
|
188
|
+
return manifest
|
|
189
|
+
|
|
190
|
+
print_warning("检测到 manifest.json 缺少或无效的 categories/tags,进入交互补全...")
|
|
191
|
+
for error in errors:
|
|
192
|
+
print_warning(error)
|
|
193
|
+
|
|
194
|
+
manifest.update(await prompt_manifest_metadata_async(manifest))
|
|
195
|
+
_finalize_manifest_metadata(manifest)
|
|
196
|
+
_save_manifest(plugin_dir, manifest)
|
|
197
|
+
|
|
198
|
+
print_success("已更新 manifest.json 中的 categories 和 tags")
|
|
199
|
+
return manifest
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
import json
|
|
6
6
|
|
|
7
|
+
from ..utils.manifest_metadata import metadata_errors
|
|
7
8
|
from .base import BaseValidator, ValidationResult
|
|
8
9
|
|
|
9
10
|
|
|
@@ -71,6 +72,13 @@ class MetadataValidator(BaseValidator):
|
|
|
71
72
|
else:
|
|
72
73
|
self.result.add_info("所有必需的元数据字段都已提供")
|
|
73
74
|
|
|
75
|
+
for error in metadata_errors(manifest_data):
|
|
76
|
+
self.result.add_error(
|
|
77
|
+
error,
|
|
78
|
+
file_path="manifest.json",
|
|
79
|
+
suggestion='请填写 "categories": ["tool|chat|fun|information|moderation"] 和 "tags": ["..."]',
|
|
80
|
+
)
|
|
81
|
+
|
|
74
82
|
# 检查推荐字段
|
|
75
83
|
missing_recommended = []
|
|
76
84
|
for field in self.RECOMMENDED_FIELDS:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{mofox_plugin_dev_toolkit-0.5.0 → mofox_plugin_dev_toolkit-0.5.1}/mpdt/dev/bridge_plugin/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{mofox_plugin_dev_toolkit-0.5.0 → mofox_plugin_dev_toolkit-0.5.1}/mpdt/dev/bridge_plugin/plugin.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{mofox_plugin_dev_toolkit-0.5.0 → mofox_plugin_dev_toolkit-0.5.1}/mpdt/templates/__init__.py
RENAMED
|
File without changes
|
{mofox_plugin_dev_toolkit-0.5.0 → mofox_plugin_dev_toolkit-0.5.1}/mpdt/templates/action_template.py
RENAMED
|
File without changes
|
{mofox_plugin_dev_toolkit-0.5.0 → mofox_plugin_dev_toolkit-0.5.1}/mpdt/templates/adapter_template.py
RENAMED
|
File without changes
|
{mofox_plugin_dev_toolkit-0.5.0 → mofox_plugin_dev_toolkit-0.5.1}/mpdt/templates/chatter_template.py
RENAMED
|
File without changes
|
|
File without changes
|
{mofox_plugin_dev_toolkit-0.5.0 → mofox_plugin_dev_toolkit-0.5.1}/mpdt/templates/config_template.py
RENAMED
|
File without changes
|
{mofox_plugin_dev_toolkit-0.5.0 → mofox_plugin_dev_toolkit-0.5.1}/mpdt/templates/event_template.py
RENAMED
|
File without changes
|
|
File without changes
|
{mofox_plugin_dev_toolkit-0.5.0 → mofox_plugin_dev_toolkit-0.5.1}/mpdt/templates/prompt_template.py
RENAMED
|
File without changes
|
{mofox_plugin_dev_toolkit-0.5.0 → mofox_plugin_dev_toolkit-0.5.1}/mpdt/templates/router_template.py
RENAMED
|
File without changes
|
{mofox_plugin_dev_toolkit-0.5.0 → mofox_plugin_dev_toolkit-0.5.1}/mpdt/templates/service_template.py
RENAMED
|
File without changes
|
{mofox_plugin_dev_toolkit-0.5.0 → mofox_plugin_dev_toolkit-0.5.1}/mpdt/templates/tool_template.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{mofox_plugin_dev_toolkit-0.5.0 → mofox_plugin_dev_toolkit-0.5.1}/mpdt/utils/color_printer.py
RENAMED
|
File without changes
|
{mofox_plugin_dev_toolkit-0.5.0 → mofox_plugin_dev_toolkit-0.5.1}/mpdt/utils/config_loader.py
RENAMED
|
File without changes
|
{mofox_plugin_dev_toolkit-0.5.0 → mofox_plugin_dev_toolkit-0.5.1}/mpdt/utils/config_manager.py
RENAMED
|
File without changes
|
|
File without changes
|
{mofox_plugin_dev_toolkit-0.5.0 → mofox_plugin_dev_toolkit-0.5.1}/mpdt/utils/license_generator.py
RENAMED
|
File without changes
|
{mofox_plugin_dev_toolkit-0.5.0 → mofox_plugin_dev_toolkit-0.5.1}/mpdt/utils/plugin_parser.py
RENAMED
|
File without changes
|
{mofox_plugin_dev_toolkit-0.5.0 → mofox_plugin_dev_toolkit-0.5.1}/mpdt/utils/template_engine.py
RENAMED
|
File without changes
|
{mofox_plugin_dev_toolkit-0.5.0 → mofox_plugin_dev_toolkit-0.5.1}/mpdt/validators/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{mofox_plugin_dev_toolkit-0.5.0 → mofox_plugin_dev_toolkit-0.5.1}/mpdt/validators/style_validator.py
RENAMED
|
File without changes
|
{mofox_plugin_dev_toolkit-0.5.0 → mofox_plugin_dev_toolkit-0.5.1}/mpdt/validators/type_validator.py
RENAMED
|
File without changes
|
|
File without changes
|