mofox-plugin-dev-toolkit 0.3.5__tar.gz → 0.4.3__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.
Files changed (69) hide show
  1. {mofox_plugin_dev_toolkit-0.3.5/mofox_plugin_dev_toolkit.egg-info → mofox_plugin_dev_toolkit-0.4.3}/PKG-INFO +1 -1
  2. {mofox_plugin_dev_toolkit-0.3.5 → mofox_plugin_dev_toolkit-0.4.3/mofox_plugin_dev_toolkit.egg-info}/PKG-INFO +1 -1
  3. {mofox_plugin_dev_toolkit-0.3.5 → mofox_plugin_dev_toolkit-0.4.3}/mofox_plugin_dev_toolkit.egg-info/SOURCES.txt +5 -0
  4. {mofox_plugin_dev_toolkit-0.3.5 → mofox_plugin_dev_toolkit-0.4.3}/mpdt/__init__.py +1 -1
  5. {mofox_plugin_dev_toolkit-0.3.5 → mofox_plugin_dev_toolkit-0.4.3}/mpdt/cli.py +22 -5
  6. mofox_plugin_dev_toolkit-0.4.3/mpdt/commands/__init__.py +3 -0
  7. mofox_plugin_dev_toolkit-0.4.3/mpdt/commands/build.py +300 -0
  8. {mofox_plugin_dev_toolkit-0.3.5 → mofox_plugin_dev_toolkit-0.4.3}/mpdt/commands/generate.py +278 -63
  9. {mofox_plugin_dev_toolkit-0.3.5 → mofox_plugin_dev_toolkit-0.4.3}/mpdt/commands/init.py +310 -132
  10. mofox_plugin_dev_toolkit-0.4.3/mpdt/dev/bridge_plugin/__init__.py +7 -0
  11. {mofox_plugin_dev_toolkit-0.3.5 → mofox_plugin_dev_toolkit-0.4.3}/mpdt/dev/bridge_plugin/cleanup_handler.py +6 -7
  12. {mofox_plugin_dev_toolkit-0.3.5 → mofox_plugin_dev_toolkit-0.4.3}/mpdt/dev/bridge_plugin/file_watcher.py +1 -1
  13. mofox_plugin_dev_toolkit-0.4.3/mpdt/dev/bridge_plugin/manifest.json +16 -0
  14. {mofox_plugin_dev_toolkit-0.3.5 → mofox_plugin_dev_toolkit-0.4.3}/mpdt/dev/bridge_plugin/plugin.py +29 -59
  15. {mofox_plugin_dev_toolkit-0.3.5 → mofox_plugin_dev_toolkit-0.4.3}/mpdt/templates/__init__.py +18 -21
  16. mofox_plugin_dev_toolkit-0.4.3/mpdt/templates/action_template.py +64 -0
  17. mofox_plugin_dev_toolkit-0.4.3/mpdt/templates/adapter_template.py +127 -0
  18. mofox_plugin_dev_toolkit-0.4.3/mpdt/templates/chatter_template.py +115 -0
  19. mofox_plugin_dev_toolkit-0.4.3/mpdt/templates/collection_template.py +104 -0
  20. mofox_plugin_dev_toolkit-0.4.3/mpdt/templates/config_template.py +57 -0
  21. mofox_plugin_dev_toolkit-0.4.3/mpdt/templates/event_template.py +101 -0
  22. mofox_plugin_dev_toolkit-0.4.3/mpdt/templates/plus_command_template.py +92 -0
  23. mofox_plugin_dev_toolkit-0.4.3/mpdt/templates/router_template.py +114 -0
  24. mofox_plugin_dev_toolkit-0.4.3/mpdt/templates/service_template.py +105 -0
  25. mofox_plugin_dev_toolkit-0.4.3/mpdt/templates/tool_template.py +90 -0
  26. {mofox_plugin_dev_toolkit-0.3.5 → mofox_plugin_dev_toolkit-0.4.3}/mpdt/utils/code_parser.py +94 -0
  27. {mofox_plugin_dev_toolkit-0.3.5 → mofox_plugin_dev_toolkit-0.4.3}/mpdt/validators/auto_fix_validator.py +266 -150
  28. mofox_plugin_dev_toolkit-0.4.3/mpdt/validators/component_validator.py +1026 -0
  29. mofox_plugin_dev_toolkit-0.4.3/mpdt/validators/config_validator.py +320 -0
  30. mofox_plugin_dev_toolkit-0.4.3/mpdt/validators/metadata_validator.py +169 -0
  31. {mofox_plugin_dev_toolkit-0.3.5 → mofox_plugin_dev_toolkit-0.4.3}/mpdt/validators/structure_validator.py +1 -1
  32. {mofox_plugin_dev_toolkit-0.3.5 → mofox_plugin_dev_toolkit-0.4.3}/pyproject.toml +1 -1
  33. mofox_plugin_dev_toolkit-0.3.5/mpdt/commands/__init__.py +0 -9
  34. mofox_plugin_dev_toolkit-0.3.5/mpdt/dev/bridge_plugin/__init__.py +0 -17
  35. mofox_plugin_dev_toolkit-0.3.5/mpdt/templates/action_template.py +0 -102
  36. mofox_plugin_dev_toolkit-0.3.5/mpdt/templates/adapter_template.py +0 -129
  37. mofox_plugin_dev_toolkit-0.3.5/mpdt/templates/chatter_template.py +0 -103
  38. mofox_plugin_dev_toolkit-0.3.5/mpdt/templates/event_template.py +0 -116
  39. mofox_plugin_dev_toolkit-0.3.5/mpdt/templates/plus_command_template.py +0 -150
  40. mofox_plugin_dev_toolkit-0.3.5/mpdt/templates/router_template.py +0 -175
  41. mofox_plugin_dev_toolkit-0.3.5/mpdt/templates/tool_template.py +0 -98
  42. mofox_plugin_dev_toolkit-0.3.5/mpdt/validators/component_validator.py +0 -842
  43. mofox_plugin_dev_toolkit-0.3.5/mpdt/validators/config_validator.py +0 -119
  44. mofox_plugin_dev_toolkit-0.3.5/mpdt/validators/metadata_validator.py +0 -107
  45. {mofox_plugin_dev_toolkit-0.3.5 → mofox_plugin_dev_toolkit-0.4.3}/LICENSE +0 -0
  46. {mofox_plugin_dev_toolkit-0.3.5 → mofox_plugin_dev_toolkit-0.4.3}/MANIFEST.in +0 -0
  47. {mofox_plugin_dev_toolkit-0.3.5 → mofox_plugin_dev_toolkit-0.4.3}/README.md +0 -0
  48. {mofox_plugin_dev_toolkit-0.3.5 → mofox_plugin_dev_toolkit-0.4.3}/mofox_plugin_dev_toolkit.egg-info/dependency_links.txt +0 -0
  49. {mofox_plugin_dev_toolkit-0.3.5 → mofox_plugin_dev_toolkit-0.4.3}/mofox_plugin_dev_toolkit.egg-info/entry_points.txt +0 -0
  50. {mofox_plugin_dev_toolkit-0.3.5 → mofox_plugin_dev_toolkit-0.4.3}/mofox_plugin_dev_toolkit.egg-info/requires.txt +0 -0
  51. {mofox_plugin_dev_toolkit-0.3.5 → mofox_plugin_dev_toolkit-0.4.3}/mofox_plugin_dev_toolkit.egg-info/top_level.txt +0 -0
  52. {mofox_plugin_dev_toolkit-0.3.5 → mofox_plugin_dev_toolkit-0.4.3}/mpdt/__main__.py +0 -0
  53. {mofox_plugin_dev_toolkit-0.3.5 → mofox_plugin_dev_toolkit-0.4.3}/mpdt/commands/check.py +0 -0
  54. {mofox_plugin_dev_toolkit-0.3.5 → mofox_plugin_dev_toolkit-0.4.3}/mpdt/commands/dev.py +0 -0
  55. {mofox_plugin_dev_toolkit-0.3.5 → mofox_plugin_dev_toolkit-0.4.3}/mpdt/dev/bridge_plugin/dev_config.py +0 -0
  56. {mofox_plugin_dev_toolkit-0.3.5 → mofox_plugin_dev_toolkit-0.4.3}/mpdt/templates/prompt_template.py +0 -0
  57. {mofox_plugin_dev_toolkit-0.3.5 → mofox_plugin_dev_toolkit-0.4.3}/mpdt/utils/__init__.py +0 -0
  58. {mofox_plugin_dev_toolkit-0.3.5 → mofox_plugin_dev_toolkit-0.4.3}/mpdt/utils/color_printer.py +0 -0
  59. {mofox_plugin_dev_toolkit-0.3.5 → mofox_plugin_dev_toolkit-0.4.3}/mpdt/utils/config_loader.py +0 -0
  60. {mofox_plugin_dev_toolkit-0.3.5 → mofox_plugin_dev_toolkit-0.4.3}/mpdt/utils/config_manager.py +0 -0
  61. {mofox_plugin_dev_toolkit-0.3.5 → mofox_plugin_dev_toolkit-0.4.3}/mpdt/utils/file_ops.py +0 -0
  62. {mofox_plugin_dev_toolkit-0.3.5 → mofox_plugin_dev_toolkit-0.4.3}/mpdt/utils/license_generator.py +0 -0
  63. {mofox_plugin_dev_toolkit-0.3.5 → mofox_plugin_dev_toolkit-0.4.3}/mpdt/utils/plugin_parser.py +0 -0
  64. {mofox_plugin_dev_toolkit-0.3.5 → mofox_plugin_dev_toolkit-0.4.3}/mpdt/utils/template_engine.py +0 -0
  65. {mofox_plugin_dev_toolkit-0.3.5 → mofox_plugin_dev_toolkit-0.4.3}/mpdt/validators/__init__.py +0 -0
  66. {mofox_plugin_dev_toolkit-0.3.5 → mofox_plugin_dev_toolkit-0.4.3}/mpdt/validators/base.py +0 -0
  67. {mofox_plugin_dev_toolkit-0.3.5 → mofox_plugin_dev_toolkit-0.4.3}/mpdt/validators/style_validator.py +0 -0
  68. {mofox_plugin_dev_toolkit-0.3.5 → mofox_plugin_dev_toolkit-0.4.3}/mpdt/validators/type_validator.py +0 -0
  69. {mofox_plugin_dev_toolkit-0.3.5 → mofox_plugin_dev_toolkit-0.4.3}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mofox-plugin-dev-toolkit
3
- Version: 0.3.5
3
+ Version: 0.4.3
4
4
  Summary: 开发工具集,用于快速创建、开发和测试 MoFox-Bot 插件
5
5
  Author-email: MoFox-Studio <wwwww95915@qq.com>
6
6
  License: GPL-3.0-or-later
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mofox-plugin-dev-toolkit
3
- Version: 0.3.5
3
+ Version: 0.4.3
4
4
  Summary: 开发工具集,用于快速创建、开发和测试 MoFox-Bot 插件
5
5
  Author-email: MoFox-Studio <wwwww95915@qq.com>
6
6
  License: GPL-3.0-or-later
@@ -12,6 +12,7 @@ mpdt/__init__.py
12
12
  mpdt/__main__.py
13
13
  mpdt/cli.py
14
14
  mpdt/commands/__init__.py
15
+ mpdt/commands/build.py
15
16
  mpdt/commands/check.py
16
17
  mpdt/commands/dev.py
17
18
  mpdt/commands/generate.py
@@ -20,15 +21,19 @@ mpdt/dev/bridge_plugin/__init__.py
20
21
  mpdt/dev/bridge_plugin/cleanup_handler.py
21
22
  mpdt/dev/bridge_plugin/dev_config.py
22
23
  mpdt/dev/bridge_plugin/file_watcher.py
24
+ mpdt/dev/bridge_plugin/manifest.json
23
25
  mpdt/dev/bridge_plugin/plugin.py
24
26
  mpdt/templates/__init__.py
25
27
  mpdt/templates/action_template.py
26
28
  mpdt/templates/adapter_template.py
27
29
  mpdt/templates/chatter_template.py
30
+ mpdt/templates/collection_template.py
31
+ mpdt/templates/config_template.py
28
32
  mpdt/templates/event_template.py
29
33
  mpdt/templates/plus_command_template.py
30
34
  mpdt/templates/prompt_template.py
31
35
  mpdt/templates/router_template.py
36
+ mpdt/templates/service_template.py
32
37
  mpdt/templates/tool_template.py
33
38
  mpdt/utils/__init__.py
34
39
  mpdt/utils/code_parser.py
@@ -4,7 +4,7 @@ MoFox Plugin Dev Toolkit
4
4
  一个用于 MoFox-Bot 插件开发的工具集
5
5
  """
6
6
 
7
- __version__ = "0.3.5"
7
+ __version__ = "0.4.3"
8
8
  __author__ = "MoFox-Studio"
9
9
  __license__ = "GPL-3.0-or-later"
10
10
 
@@ -67,7 +67,7 @@ def init(ctx: click.Context, plugin_name: str | None, template: str, author: str
67
67
 
68
68
 
69
69
  @cli.command()
70
- @click.argument("component_type", type=click.Choice(["action", "tool", "event", "adapter", "prompt", "plus-command","router","chatter"]), required=False)
70
+ @click.argument("component_type", type=click.Choice(["action", "tool", "event", "adapter", "plus-command", "router", "chatter", "service", "config"]), required=False)
71
71
  @click.argument("component_name", required=False)
72
72
  @click.option("--description", "-d", help="组件描述")
73
73
  @click.option("--output", "-o", type=click.Path(), help="输出目录")
@@ -135,14 +135,31 @@ def check(ctx: click.Context, path: str, level: str, fix: bool, report: str, out
135
135
 
136
136
 
137
137
  @cli.command()
138
+ @click.argument("plugin_path", type=click.Path(), required=False, default=".")
138
139
  @click.option("--output", "-o", type=click.Path(), default="dist", help="输出目录")
139
140
  @click.option("--with-docs", is_flag=True, help="包含文档")
140
- @click.option("--format", type=click.Choice(["zip", "tar.gz", "wheel"]), default="zip", help="构建格式")
141
+ @click.option("--format", "fmt", type=click.Choice(["mfp", "zip"]), default="mfp", help="构建格式(mfp 为推荐格式)")
141
142
  @click.option("--bump", type=click.Choice(["major", "minor", "patch"]), help="自动升级版本号")
142
143
  @click.pass_context
143
- def build(ctx: click.Context, output: str, with_docs: bool, format: str, bump: str | None) -> None:
144
- """构建和打包插件"""
145
- console.print("[yellow]⚠️ 构建命令尚未实现[/yellow]")
144
+ def build(ctx: click.Context, plugin_path: str, output: str, with_docs: bool, fmt: str, bump: str | None) -> None:
145
+ """构建并打包插件为 .mfp 文件
146
+
147
+ PLUGIN_PATH 为插件根目录(含 manifest.json),默认为当前目录。
148
+ """
149
+ from mpdt.commands.build import build_plugin
150
+
151
+ try:
152
+ build_plugin(
153
+ plugin_path=plugin_path,
154
+ output_dir=output,
155
+ with_docs=with_docs,
156
+ fmt=fmt,
157
+ bump=bump,
158
+ verbose=ctx.obj["verbose"],
159
+ )
160
+ except Exception as e:
161
+ console.print(f"[bold red]❌ 构建失败: {e}[/bold red]")
162
+ raise click.Abort()
146
163
 
147
164
 
148
165
  @cli.command()
@@ -0,0 +1,3 @@
1
+ """
2
+ 命令模块
3
+ """
@@ -0,0 +1,300 @@
1
+ """
2
+ 构建命令实现
3
+
4
+ 将插件目录打包为 .mfp 文件(本质为 ZIP 压缩包)。
5
+ loader.py 支持从 .mfp 直接加载插件,与文件夹加载行为一致。
6
+
7
+ 流程:
8
+ 1. 验证插件目录及 manifest.json
9
+ 2. 可选地自动升级版本号(major / minor / patch)
10
+ 3. 收集需打包的文件(排除缓存、版本控制等)
11
+ 4. 写入 .mfp(ZIP)或 .zip 文件
12
+ 5. 打印构建摘要
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ import json
18
+ import re
19
+ import zipfile
20
+ from pathlib import Path
21
+
22
+ from rich.panel import Panel
23
+ from rich.table import Table
24
+
25
+ from mpdt.utils.color_printer import (
26
+ console,
27
+ print_error,
28
+ print_step,
29
+ print_success,
30
+ print_warning,
31
+ )
32
+
33
+ # 构建时默认排除的文件/目录名称(精确匹配)
34
+ _EXCLUDE_NAMES: set[str] = {
35
+ "__pycache__",
36
+ ".git",
37
+ ".gitignore",
38
+ ".gitattributes",
39
+ ".github",
40
+ ".mypy_cache",
41
+ ".ruff_cache",
42
+ ".pytest_cache",
43
+ ".venv",
44
+ "venv",
45
+ ".env",
46
+ "node_modules",
47
+ "dist",
48
+ ".DS_Store",
49
+ "Thumbs.db",
50
+ }
51
+
52
+ # 排除的文件扩展名
53
+ _EXCLUDE_EXTS: set[str] = {
54
+ ".pyc",
55
+ ".pyo",
56
+ ".pyd",
57
+ ".egg-info",
58
+ }
59
+
60
+ # 文档相关目录/文件名
61
+ _DOC_NAMES: set[str] = {
62
+ "docs",
63
+ "doc",
64
+ "README.md",
65
+ "README.rst",
66
+ "CHANGELOG.md",
67
+ "CHANGELOG.rst",
68
+ }
69
+
70
+
71
+ def _is_excluded(path: Path, with_docs: bool) -> bool:
72
+ """判断路径是否应被排除。"""
73
+ name = path.name
74
+ if name in _EXCLUDE_NAMES:
75
+ return True
76
+ if path.suffix in _EXCLUDE_EXTS:
77
+ return True
78
+ # 以 . 开头的隐藏文件/目录
79
+ if name.startswith("."):
80
+ return True
81
+ # 文档文件(若不包含文档)
82
+ if not with_docs and name in _DOC_NAMES:
83
+ return True
84
+ return False
85
+
86
+
87
+ def _collect_files(plugin_dir: Path, with_docs: bool) -> list[Path]:
88
+ """递归收集插件目录中需要打包的文件列表(相对路径)。"""
89
+ files: list[Path] = []
90
+
91
+ for item in sorted(plugin_dir.rglob("*")):
92
+ # 检查路径上的每个部分是否被排除
93
+ relative = item.relative_to(plugin_dir)
94
+ parts = relative.parts
95
+
96
+ excluded = False
97
+ for part in parts:
98
+ part_path = Path(part)
99
+ if _is_excluded(part_path, with_docs):
100
+ excluded = True
101
+ break
102
+
103
+ if excluded:
104
+ continue
105
+
106
+ if item.is_file():
107
+ files.append(item)
108
+
109
+ return files
110
+
111
+
112
+ def _bump_version(version: str, bump: str) -> str:
113
+ """按规则升级版本号(语义化版本 major.minor.patch)。
114
+
115
+ Args:
116
+ version: 当前版本字符串,例如 "1.2.3"
117
+ bump: 升级类型,"major" / "minor" / "patch"
118
+
119
+ Returns:
120
+ 升级后的版本字符串
121
+
122
+ Raises:
123
+ ValueError: 版本格式不合法
124
+ """
125
+ match = re.fullmatch(r"(\d+)\.(\d+)\.(\d+)(.*)", version.strip())
126
+ if not match:
127
+ raise ValueError(f"版本号格式不合法: '{version}',应为 major.minor.patch")
128
+
129
+ major, minor, patch, suffix = int(match.group(1)), int(match.group(2)), int(match.group(3)), match.group(4)
130
+
131
+ if bump == "major":
132
+ major += 1
133
+ minor = 0
134
+ patch = 0
135
+ elif bump == "minor":
136
+ minor += 1
137
+ patch = 0
138
+ elif bump == "patch":
139
+ patch += 1
140
+
141
+ return f"{major}.{minor}.{patch}{suffix}"
142
+
143
+
144
+ def _load_manifest(plugin_dir: Path) -> dict | None:
145
+ """读取并解析 manifest.json,返回字典;失败返回 None。"""
146
+ manifest_path = plugin_dir / "manifest.json"
147
+ if not manifest_path.exists():
148
+ print_error(f"manifest.json 不存在: {manifest_path}")
149
+ return None
150
+ try:
151
+ with open(manifest_path, encoding="utf-8") as f:
152
+ return json.load(f)
153
+ except json.JSONDecodeError as e:
154
+ print_error(f"manifest.json 解析失败: {e}")
155
+ return None
156
+
157
+
158
+ def _save_manifest(plugin_dir: Path, manifest: dict) -> None:
159
+ """将 manifest 字典写回 manifest.json。"""
160
+ manifest_path = plugin_dir / "manifest.json"
161
+ with open(manifest_path, "w", encoding="utf-8") as f:
162
+ json.dump(manifest, f, ensure_ascii=False, indent=4)
163
+
164
+
165
+ def build_plugin(
166
+ plugin_path: str = ".",
167
+ output_dir: str = "dist",
168
+ with_docs: bool = False,
169
+ fmt: str = "mfp",
170
+ bump: str | None = None,
171
+ verbose: bool = False,
172
+ ) -> None:
173
+ """构建并打包插件为 .mfp / .zip 文件。
174
+
175
+ Args:
176
+ plugin_path: 插件根目录(包含 manifest.json)
177
+ output_dir: 输出目录,默认 dist/
178
+ with_docs: 是否将文档文件打入包中
179
+ fmt: 输出格式,"mfp"(推荐) 或 "zip"
180
+ bump: 自动升级版本,"major" / "minor" / "patch" / None
181
+ verbose: 是否显示详细信息
182
+ """
183
+ plugin_dir = Path(plugin_path).resolve()
184
+
185
+ # ── 1. 基本验证 ──────────────────────────────────────────────────────────
186
+ if not plugin_dir.exists():
187
+ print_error(f"插件路径不存在: {plugin_dir}")
188
+ return
189
+ if not plugin_dir.is_dir():
190
+ print_error(f"插件路径不是目录: {plugin_dir}")
191
+ return
192
+
193
+ console.print(Panel.fit(f"📦 构建插件: [cyan]{plugin_dir.name}[/cyan]", border_style="blue"))
194
+
195
+ manifest = _load_manifest(plugin_dir)
196
+ if manifest is None:
197
+ return
198
+
199
+ required = ["name", "version", "description", "author", "entry_point"]
200
+ for field in required:
201
+ if field not in manifest:
202
+ print_error(f"manifest.json 缺少必需字段: '{field}'")
203
+ return
204
+
205
+ plugin_name: str = manifest["name"]
206
+ plugin_version: str = manifest["version"]
207
+
208
+ # ── 2. 版本升级 ──────────────────────────────────────────────────────────
209
+ if bump:
210
+ try:
211
+ new_version = _bump_version(plugin_version, bump)
212
+ except ValueError as e:
213
+ print_error(str(e))
214
+ return
215
+ print_step(f"版本升级: [yellow]{plugin_version}[/yellow] → [green]{new_version}[/green]")
216
+ manifest["version"] = new_version
217
+ _save_manifest(plugin_dir, manifest)
218
+ plugin_version = new_version
219
+
220
+ # ── 3. 验证入口文件 ───────────────────────────────────────────────────────
221
+ entry_point = manifest.get("entry_point", "plugin.py")
222
+ entry_file = plugin_dir / entry_point
223
+ if not entry_file.exists():
224
+ print_warning(f"入口文件不存在: {entry_point}(仍将继续构建)")
225
+
226
+ # ── 4. 收集文件 ───────────────────────────────────────────────────────────
227
+ print_step("收集文件...")
228
+ files = _collect_files(plugin_dir, with_docs)
229
+
230
+ if not files:
231
+ print_warning("未找到任何需要打包的文件")
232
+ return
233
+
234
+ if verbose:
235
+ for f in files:
236
+ rel = f.relative_to(plugin_dir)
237
+ console.print(f" [dim]+ {rel}[/dim]")
238
+
239
+ # ── 5. 确定输出路径 ───────────────────────────────────────────────────────
240
+ # output_dir 可以是绝对路径或相对于插件目录
241
+ out_path = Path(output_dir)
242
+ if not out_path.is_absolute():
243
+ out_path = plugin_dir / output_dir
244
+
245
+ out_path.mkdir(parents=True, exist_ok=True)
246
+
247
+ suffix = ".mfp" if fmt == "mfp" else ".zip"
248
+ archive_name = f"{plugin_name}-{plugin_version}{suffix}"
249
+ archive_path = out_path / archive_name
250
+
251
+ if archive_path.exists():
252
+ print_warning(f"目标文件已存在,将覆盖: {archive_path}")
253
+
254
+ # ── 6. 压缩打包 ───────────────────────────────────────────────────────────
255
+ print_step(f"正在写入 {suffix} 包...")
256
+ total_bytes = 0
257
+
258
+ try:
259
+ with zipfile.ZipFile(archive_path, "w", compression=zipfile.ZIP_DEFLATED) as zf:
260
+ for file_path in files:
261
+ arcname = file_path.relative_to(plugin_dir)
262
+ zf.write(file_path, arcname)
263
+ total_bytes += file_path.stat().st_size
264
+ if verbose:
265
+ console.print(f" [dim] → {arcname}[/dim]")
266
+ except Exception as e:
267
+ print_error(f"打包失败: {e}")
268
+ # 清理不完整的文件
269
+ if archive_path.exists():
270
+ archive_path.unlink()
271
+ return
272
+
273
+ # ── 7. 摘要 ──────────────────────────────────────────────────────────────
274
+ archive_size = archive_path.stat().st_size
275
+
276
+ table = Table(show_header=False, box=None, padding=(0, 2))
277
+ table.add_column(style="cyan")
278
+ table.add_column(style="white")
279
+ table.add_row("插件名称", plugin_name)
280
+ table.add_row("版本", plugin_version)
281
+ table.add_row("作者", manifest.get("author", "-"))
282
+ table.add_row("入口文件", entry_point)
283
+ table.add_row("打包文件数", str(len(files)))
284
+ table.add_row("原始大小", _format_size(total_bytes))
285
+ table.add_row("包大小", _format_size(archive_size))
286
+ table.add_row("输出路径", str(archive_path))
287
+
288
+ console.print()
289
+ console.print(table)
290
+ console.print()
291
+ print_success(f"构建完成: {archive_path.name}")
292
+
293
+
294
+ def _format_size(size: int) -> str:
295
+ """将字节数格式化为人类可读字符串。"""
296
+ for unit in ("B", "KB", "MB", "GB"):
297
+ if size < 1024:
298
+ return f"{size:.1f} {unit}"
299
+ size //= 1024
300
+ return f"{size:.1f} TB"