打包插件

本文档介绍如何将开发完成的插件打包为正式发布版本。

一、开发目录 vs 正式目录

textwork 插件为例:

1.1 开发目录结构

plugins/textwork/
├── manifest.json
├── backend/
│   ├── main.py
│   ├── handlers.py              # 源码
│   ├── utils/                   # 源码
│   └── compile/
│       └── handlers.cp310-win_amd64.pyd
└── frontend/
    ├── src/                     # 源码
    ├── node_modules/            # 依赖
    └── dist/
        └── index.html

1.2 正式目录结构

textwork/
├── manifest.json
├── backend/
│   ├── main.py
│   └── handlers.cp310-win_amd64.pyd
└── frontend/
    └── dist/
        ├── index.html
        └── logo.png

二、打包步骤

2.1 编译后端

cd plugins/my-plugin/backend
nuitka --include-module=utils --output-dir=compile --module handlers.py

2.2 编译前端

cd plugins/my-plugin/frontend
pnpm build

2.3 组织正式目录

创建正式插件目录,只保留必要文件:

需要保留 说明
manifest.json 插件配置
backend/main.py 入口文件
backend/handlers.*.pyd 从 compile/ 复制
frontend/dist/ 整个目录
不需要 说明
backend/handlers.py 源码
backend/utils/ 源码目录
backend/compile/handlers.build/ 中间文件
frontend/src/ 前端源码
frontend/node_modules/ 前端依赖

2.4 压缩为 7z

使用任意压缩软件(如 360压缩、7-Zip、WinRAR、Bandizip 等)将正式目录压缩为 .7z 格式:

my-plugin.7z

注意:压缩时选择插件目录本身,确保解压后得到的是完整的插件目录,而不是目录里面的文件。

正确:

my-plugin.7z 解压后 → my-plugin/
                      ├── manifest.json
                      ├── backend/
                      └── frontend/

错误:

my-plugin.7z 解压后 → manifest.json
                      backend/
                      frontend/

三、批量导出脚本(可选)

项目根目录提供了 move_to_plugin.py 脚本,可批量将编译结果导出为正式插件。

3.1 功能说明

  • 扫描 plugins/ 目录中的所有插件
  • 复制到 out/xiaowo/plugins/ 目录
  • 自动跳过 dev_mode: true 的插件
  • backend 只复制 main.py.pyd 文件
  • frontend 只复制 dist/ 目录
  • data 目录完整复制

3.2 使用方式

python move_to_plugin.py

3.3 脚本代码

"""
插件复制脚本
扫描 plugins 目录中的所有插件,复制到 out/xiaowo/plugins 目录
- backend 目录只复制 main.py 和 compile 目录中的 .pyd 文件
- frontend 目录只复制 dist 目录
- data 目录完整复制
- manifest.json 复制
"""

import json
import os
import shutil
from pathlib import Path

# 源目录和目标目录
SOURCE_DIR = Path(r"plugins").resolve()
TARGET_DIR = Path(r"out\xiaowo\plugins").resolve()


def get_backend_dir_from_manifest(plugin_path: Path, manifest_data: dict) -> Path:
    """
    从 manifest.json 获取后端目录
    优先读取 dev.backend.entry
    """
    entry = None

    # 优先读取 dev.backend.entry
    dev = manifest_data.get('dev')
    if dev:
        dev_backend = dev.get('backend')
        if dev_backend:
            entry = dev_backend.get('entry')

    # 如果没有从 dev 获取到 entry,则从 backend 获取
    if not entry:
        backend = manifest_data.get('backend')
        if backend:
            entry = backend.get('entry')

    if not entry:
        return plugin_path / "backend"

    entry_path = Path(entry)
    if entry_path.is_absolute():
        return entry_path.parent
    else:
        return (plugin_path / entry).parent


def copy_plugin(plugin_name: str, plugin_path: Path, manifest_data: dict = None):
    """复制单个插件到目标目录(保留原有文件,覆盖同名文件)"""
    target_plugin_path = TARGET_DIR / plugin_name

    # 创建目标插件目录(如果不存在)
    target_plugin_path.mkdir(parents=True, exist_ok=True)

    # 复制 manifest.json
    manifest_file = plugin_path / "manifest.json"
    if manifest_file.exists():
        shutil.copy2(manifest_file, target_plugin_path / "manifest.json")
        print(f"  复制 manifest.json")

    # 复制 backend 目录(只复制 main.py 和 compile 目录中的 .pyd 文件)
    # 根据 manifest 获取实际的后端目录
    backend_dir = get_backend_dir_from_manifest(plugin_path, manifest_data) if manifest_data else plugin_path / "backend"
    if backend_dir.exists():
        target_backend_dir = target_plugin_path / "backend"
        target_backend_dir.mkdir(parents=True, exist_ok=True)

        # 复制 main.py 和所有以 _main.py 结尾的文件
        main_py = backend_dir / "main.py"
        if main_py.exists():
            shutil.copy2(main_py, target_backend_dir / "main.py")
            print(f"  复制 backend/main.py (来源: {backend_dir})")

        # 复制所有以 _main.py 结尾的文件
        for main_file in backend_dir.glob("*_main.py"):
            shutil.copy2(main_file, target_backend_dir / main_file.name)
            print(f"  复制 backend/{main_file.name}")

        # 复制 compile 目录中的 .pyd 文件到 backend 目录(与 main.py 同级)
        compile_dir = backend_dir / "compile"
        if compile_dir.exists():
            pyd_files = list(compile_dir.glob("*.pyd"))
            for pyd_file in pyd_files:
                shutil.copy2(pyd_file, target_backend_dir / pyd_file.name)
                print(f"  复制 backend/{pyd_file.name}")

            if not pyd_files:
                print(f"  backend/compile 目录中没有找到 .pyd 文件")

    # 复制 frontend/dist 目录(保留原有文件)
    frontend_dist_dir = plugin_path / "frontend" / "dist"
    if frontend_dist_dir.exists():
        target_frontend_dir = target_plugin_path / "frontend"
        target_frontend_dir.mkdir(parents=True, exist_ok=True)
        shutil.copytree(frontend_dist_dir, target_frontend_dir / "dist", dirs_exist_ok=True)
        print(f"  复制 frontend/dist 目录")

    # 复制 data 目录(完整复制,排除 try_radio 目录)
    data_dir = plugin_path / "data"
    if data_dir.exists():
        def ignore_try_radio(dir, files):
            # 如果当前目录是 data 目录,排除 try_radio
            if Path(dir) == data_dir:
                return ['try_radio',"hotwords.json"]
            return []

        shutil.copytree(data_dir, target_plugin_path / "data",
                       ignore=ignore_try_radio, dirs_exist_ok=True)
        print(f"  复制 data 目录 (排除 try_radio)")


def main():
    """主函数:扫描并复制所有插件"""
    if not SOURCE_DIR.exists():
        print(f"源目录不存在: {SOURCE_DIR}")
        return

    # 确保目标目录存在
    TARGET_DIR.mkdir(parents=True, exist_ok=True)

    # 扫描所有插件目录
    plugins = [d for d in SOURCE_DIR.iterdir() if d.is_dir()]

    if not plugins:
        print("没有找到任何插件")
        return

    print(f"找到 {len(plugins)} 个插件")
    print("-" * 40)

    copied_count = 0
    skipped_count = 0
    for plugin_path in plugins:
        plugin_name = plugin_path.name

        # 检查 manifest.json 中的 dev_mode
        manifest_file = plugin_path / "manifest.json"
        if manifest_file.exists():
            with open(manifest_file, "r", encoding="utf-8") as f:
                manifest_data = json.load(f)
            if manifest_data.get("dev_mode") is True:
                print(f"跳过插件: {plugin_name} (dev_mode 为 true)")
                skipped_count += 1
                continue

        print(f"正在复制插件: {plugin_name}")
        copy_plugin(plugin_name, plugin_path, manifest_data)
        copied_count += 1
        print()

    print("-" * 40)
    print(f"复制完成! 共 {copied_count} 个插件, 跳过 {skipped_count} 个 (dev_mode)")


if __name__ == "__main__":
    main()

四、注意事项

  1. .pyd 文件名包含 Python 版本(如 cp310 表示 Python 3.10)
  2. 确保 manifest.json 中的路径配置正确
  3. 如有 data/ 目录存放默认配置,也需要一并复制
0个回答默认排序 投票数排序
还没有回答~
请先登录