关于 create-moviepilot-plugin
create-moviepilot-plugin 是用于从本地源码创建或修改 MoviePilot 插件的开发技能,面向 MoviePilot 插件开发者。它根据宿主版本指导目录布局、实现方式和验证,并将安装及运行时更改限制在用户授权范围内。
主要功能
- 01按版本选择插件目录布局
- 02后端与 UI 模式指导
- 03本地源码和包元数据流程
- 04宿主 API 与扩展点核验
- 05范围受限的编辑与验证指导
适用场景
- 01为 MoviePilot 宿主创建插件
- 02修改现有本地插件
- 03选择插件 UI 实现模式
MCP 相关
README 未说明 MCP 接口或 MCP 专用功能。
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ jxxghp/ moviepilot --skill create-moviepilot-plugin安装后在 Claude Code 中输入/create-moviepilot-plugin确认已加载。
详细介绍
创建 MoviePilot 插件
使用此技能构建或修改 MoviePilot 插件。此类插件可从本地插件源开发,并安装到正在运行的 MoviePilot 实例中。
事实依据
- 宿主插件契约:
app/plugins/__init__.py,尤其是_PluginBase。 - 宿主插件发现、本地源同步、安装和重新加载:
app/runtime/extensions/plugin_manager.py和app/adapters/external/market.py。 - 宿主插件端点、API 身份验证、静态文件、远程服务和侧边栏导航:
app/api/endpoints/plugin.py。 - 本地开发说明:
docs/development-setup.md。 - 首先根据源代码、版本和目标插件仓库,确定宿主实际使用的代际。MoviePilot V3 使用
plugins.v3/和package.v3.json;V2 使用plugins.v2/和package.v2.json;旧版 V1 使用plugins/和package.json。对于 V3 宿主,请勿生成 V2 路径。 - 在
MoviePilot-Plugins中或基于其进行开发时,请阅读其README.md、docs/Repository_Guide.md以及目标代际的指南: V3 阅读docs/Plugin_Development.md,V2 阅读docs/V2_Plugin_Development.md。 对于特定场景的扩展,请阅读对应的docs/faq/*.md。 - 对于 V3,优先使用当前宿主和指南中记录的公开
app.sdk.*入口点;不要照搬 V2 示例中的旧版内部导入路径。使用模板前,请对照已安装的宿主核实扩展签名。
任务范围
创建或修复插件的请求,授权进行相关的本地源代码修改和验证。应沿用此授权;但这本身并不意味着获准修改宿主配置、安装或重新加载插件、重启或发布。只有在用户提出这些要求或已批准相应范围时,才执行这些步骤,并遵守宿主的确认要求。编辑正在运行且受监视的插件源代码前,请检查自动重新加载设置;此类编辑可能立即影响运行环境,因此必须确保该运行时影响在授权范围内。
代码工具工作流
- 使用
execute_command(action="run"),通过rg配合范围较窄的 glob 模式或路径,定位插件类、扩展点、测试、包条目和目录内容。 - 编辑前,阅读相关实现及相邻示例。
- 如果
read_file报告内容被截断,请缩小start_line和end_line范围,继续读取,直到检查完所有相关部分。 - 使用 Python 或 Node.js 依赖 API 前,先根据 requirements、包清单、锁文件、本地包源码以及
.pyi/.d.ts声明,确定准确的已安装版本或锁定版本。如果这些信息不够,请使用search_web搜索官方文档域名,并通过browse_webpage阅读对应版本的文档。不要凭记忆猜测 API 签名,也不要混用不同主版本的示例。请直接使用rg搜索相关包目录、.venv或node_modules,不要不加限制地扫描整个项目。 - 根据修改范围选择编辑工具。如果一项逻辑改动涉及多个文件、新增文件或删除文件,请使用
apply_patch:提交一个用*** Begin Patch/*** End Patch包裹的补丁,并包含*** Add File:、*** Update File:和*** Delete File:部分;所有上下文和删除行都必须与当前内容完全一致。 - 单个文件中的局部改动请使用
edit_file。默认情况下,其old_text必须精确标识一个位置;应添加周围上下文,而不是启用replace_all,除非确实要更改所有匹配项。 - 新建单个独立文件请使用
write_file。要完整重写现有文件,必须设置overwrite=true;替换已读取的内容前,先调用read_file(include_metadata=true),并将其sha256作为expected_sha256传入。 - 短时验证、Git 和诊断命令请使用
execute_command(action="run")。仅交互式或长时间运行的命令使用action="start",然后通过返回的会话 ID 继续操作。 - 阅读结构化命令结果:只有
execution_outcome="succeeded"且exit_code=0才表示命令已完成并通过。命令失败时检查output,日志被截断时检查output_file。超时或结果未知并不代表副作用已撤销,不要盲目重试写入操作。 - 对后台命令,使用
output_until_seq和output_until_offset分别作为since_seq和since_offset恢复读取。读取部分数据块时从偏移量 0 开始;last_seq不是已读取位置。以有限时间间隔等待新输出,并逐页读取剩余内容,直到output_complete=true,确保日志完整。如有output_lost缺口,请报告。 - 写入、启动或终止操作后如出现
output_error,需要在合适的字节数限制下再次读取,而不是重新执行该操作。 - 不要使用 shell 重定向或内联脚本编辑源文件,也不要借此绕过文件工具的权限错误。
- 如果插件使用 Vue federation,还需阅读
MoviePilot-Frontend/docs/module-federation-guide.md、MoviePilot-Frontend/docs/federation-troubleshooting.md、MoviePilot-Frontend/src/utils/federationLoader.ts和MoviePilot-Frontend/src/pages/plugin-app.vue。 - 仓库边界:
MoviePilot负责运行时加载、API 注册、事件、服务、数据和权限;MoviePilot-Frontend负责插件 UI 渲染、联邦加载和侧边栏页面;MoviePilot-Plugins负责插件源码、图标、包索引和发布元数据。
开始前
- 理解用户的请求:插件用途、触发模式、配置、输出界面,以及是否需要调度器、API、命令、工作流操作或 Agent 工具。
- 编写任何 UI 代码之前,先执行 UI 模式选择关卡。
- 如果用户已明确选择 JSON 配置/Vuetify JSON 或 Vue 联邦,则按用户选择执行。
- 如果插件包含任何 UI 界面,而用户尚未选择模式,请让用户从以下两种模式中选择,并等待其答复后再实现 UI 文件或架构。
- 不要仅仅因为某种模式看起来更简单,就默认采用该模式。
- 创建新插件前,检查现有插件:
- 本地运行时示例:
app/plugins/<plugin>/__init__.py - 市场/本地源码候选项:运行实例可用时,通过
operation_id=plugin.market调用moviepilot_api。 - 已安装插件候选项:使用
operation_id=plugin.installed;如果可从本地插件仓库或插件市场元数据匹配到源码,其摘要中会包含repo_url。 - Vue 联邦示例:使用对应版本代际的现有插件,以及
MoviePilot-Frontend/examples/plugin-component/。
- 本地运行时示例:
- 确定目标源码路径:
- 尽可能使用
operation_id=config.system.get查询PLUGIN_LOCAL_REPO_PATHS。 - 如果只配置了一个本地插件仓库,优先使用该路径。
- 如果配置了多个仓库,则使用用户指定的仓库;否则询问用户要使用哪个仓库。
- 如果没有配置仓库,则在合适的本地目录中准备源码。如果已获授权进行本地安装/配置,则使用
operation_id=config.system.update注册该仓库,并设置setting_key="PLUGIN_LOCAL_REPO_PATHS"、将所选源码路径作为value,以及设置operation="replace"。相对路径(如local-plugins)会相对于 MoviePilot 根目录解析。 将插件写入所选源码目录;除非用户明确要求仅进行运行时实验,否则不要直接写入app/plugins/。
- 尽可能使用
- 选择插件 ID:
- 类名就是插件 ID,例如
MyNotifier。 - 目录名为类名的小写形式,例如
mynotifier。 - 除非用户明确要求修改该插件,否则避免与已安装或市场中的插件发生冲突。
- 如果插件可能支持克隆,不要将原始插件 ID 硬编码为数据/配置命名空间;请使用
self.__class__.__name__。
- 类名就是插件 ID,例如
UI 模式选择关卡
MoviePilot 插件 UI 恰好有两种实现模式。如果请求包含配置、详情页、仪表板、侧边栏页面或任何其他插件 UI,且未明确指定模式,则必须让用户选择一种。
可以这样简洁地询问:
这个插件 UI 用哪种方式实现?
1. JSON 配置:后端返回 Vuetify JSON,适合普通配置表单、简单详情页和轻量仪表板。
2. 联邦 UI:独立 Vue 远程组件,适合复杂交互、自定义布局、侧栏全页或多页面。
选择规则:
- JSON 配置 / Vuetify JSON:使用 JSON 组件架构实现
get_form()、get_page()和get_dashboard()。无需前端构建,也无需dist/assets/remoteEntry.js。 - 联邦 UI / Vue 远程组件:实现
get_render_mode(),通过 Vite 联邦暴露 Vue 组件,将前端资源构建到插件目录中;只有在用户要求侧边栏页面时才使用get_sidebar_nav()。 - 如果插件确实没有面向用户的 UI,请说明无需选择 UI 模式,并仅实现请求所需的后端扩展点。
- 等待期间可以继续进行后端工作,但前提是该工作不会限制或妨碍任一种 UI 模式。
本地源代码布局
对于已验证的 V3 主机,新建本地插件时请使用此布局。对于 V2,请改用
package.v2.json 和 plugins.v2/,并遵循其依赖契约。
<local-plugin-repo>/
├── package.v3.json
└── plugins.v3/
└── <plugin_id_lower>/
├── __init__.py
├── pyproject.toml # only when extra runtime dependencies are necessary
└── ... # helper modules, schemas, static assets
对于 Vue 联邦插件,运行时要求插件目录下包含已构建的远程资源:
plugins.v3/<plugin_id_lower>/
├── __init__.py
├── dist/
│ └── assets/
│ ├── remoteEntry.js
│ └── ... # JS/CSS/assets referenced by remoteEntry
├── package.json # optional frontend build project metadata
├── vite.config.js # optional frontend build config
└── src/ # optional source, not required at runtime
不要依赖运行时的前端源文件。如果为便于维护而在插件仓库中保留源代码,仍需构建并随插件提供 remoteEntry.js 所需的 dist/assets 文件。
仅当用户明确需要时才使用旧版布局:
<local-plugin-repo>/
├── package.json
└── plugins/
└── <plugin_id_lower>/
└── __init__.py
对于应在 V2 上运行的旧版 package.json 条目,请包含 "v2": true。
对于当前的 V3 开发,请使用 package.v3.json 和 plugins.v3/。
软件包元数据
为插件 ID 添加或更新软件包条目。确保软件包版本与类的 plugin_version 保持一致。
{
"MyNotifier": {
"name": "通知示例",
"description": "根据用户配置发送示例通知。",
"labels": "消息通知",
"version": "1.0.0",
"icon": "mynotifier.png",
"author": "local",
"level": 1,
"system_version": ">=3.0.0",
"history": {
"v1.0.0": "初始版本"
}
}
}
规则:
- 软件包对象键必须与插件类名一致。
version必须与plugin_version一致。- 如果插件类具有
plugin_name、plugin_desc、plugin_icon、plugin_author、plugin_label、auth_level等属性,则name、description、icon、author、labels和level应与之匹配。 history应记录每个已发布版本的用户可读变更。- 如果插件依赖特定 MoviePilot 版本引入的主机功能,请使用
system_version;此类功能包括新的后端 API、辅助函数、事件、Vue 联邦行为、侧边栏导航、仪表盘行为或 Agent 工具。 - 仅当插件有意通过 GitHub Release 归档进行分发时,才使用
"release": true。 - 新插件条目通常应追加到软件包索引中,以便在市场中显示为较新的项目。
- 将
system_version设为提供所用 API 的实际最低版本;示例并不能证明每项主机功能都已在 3.0.0 中提供。 - 除非确有需要,否则不要添加依赖。请使用对应主机版本的清单契约(当前 V3 使用
pyproject.toml)。依赖变更需要经过主机安装流程;仅热重载不会安装依赖。 - 插件依赖会安装到共享的 MoviePilot Python 环境中。除非用户已明确接受兼容性风险,否则不要锁定或降级 MoviePilot 已提供的软件包。
实现框架
实现 _PluginBase 中的所有抽象方法。所有新函数和方法都必须编写中文文档字符串;公共类、公共方法和公共函数是评审的硬性门槛。
from typing import Any, Dict, List, Optional, Tuple
from app.plugins import _PluginBase
class MyNotifier(_PluginBase):
"""通知示例插件。"""
plugin_name = "通知示例"
plugin_desc = "根据用户配置发送示例通知。"
plugin_icon = "mynotifier.png"
plugin_version = "1.0.0"
plugin_label = "消息通知"
plugin_author = "local"
plugin_config_prefix = "mynotifier_"
plugin_order = 100
auth_level = 1
_enabled = False
_message = ""
def init_plugin(self, config: dict = None) -> None:
"""根据插件配置初始化运行状态。"""
self.stop_service()
self._enabled = False
self._message = ""
if not config:
return
self._enabled = bool(config.get("enabled"))
self._message = str(config.get("message") or "")
def get_state(self) -> bool:
"""获取插件启用状态。"""
return self._enabled
@staticmethod
def get_command() -> List[Dict[str, Any]]:
"""返回插件远程命令列表。"""
return []
def get_api(self) -> List[Dict[str, Any]]:
"""返回插件 API 列表。"""
return []
def get_form(self) -> Tuple[Optional[List[dict]], Dict[str, Any]]:
"""返回插件配置表单与默认配置。"""
return [
{
"component": "VForm",
"content": [
{
"component": "VSwitch",
"props": {
"model": "enabled",
"label": "启用插件"
}
},
{
"component": "VTextField",
"props": {
"model": "message",
"label": "通知内容"
}
}
]
}
], {
"enabled": False,
"message": ""
}
def get_page(self) -> Optional[List[dict]]:
"""返回插件详情页面。"""
if not self._enabled:
return None
return [
{
"component": "VAlert",
"props": {
"type": "info",
"text": self._message or "插件已启用"
}
}
]
def stop_service(self) -> None:
"""停止插件后台服务并释放资源。"""
return None
扩展点
仅使用请求的插件实际需要的扩展点:
- 配置:
get_form()返回 Vuetify 表单架构和默认数据;init_plugin()读取配置;update_config()持久化内部变更。 - 数据:使用
save_data()、get_data()、del_data()和get_data_path()。 - 通知:使用
post_message(),而不是直接调用消息模块。 - API:从
get_api()返回路由定义;省略auth时,默认身份验证方式为apikey。Vue 组件 API 通常应使用auth: "bear",并通过前端传入的apiprop 调用。 - 命令:从
get_command()返回斜杠命令定义,并通过 MoviePilot 事件分发。 - 服务:从
get_service()返回调度器服务,并始终在stop_service()中清理。 - 一次性或延迟作业(例如“现在运行一次”或“事件发生后 N 秒运行”):调用
app.sdk.scheduler.add_plugin_once_job(plugin_id, job_id, self.method, name, delay_seconds=..., func_kwargs=...),而不是创建私有BackgroundScheduler。此方法不会重置插件的周期服务;如果存在相同 ID 的待处理作业,则会替换该作业;插件重新加载或卸载时会清除该作业。传入插件实例的绑定方法;如果插件停止后不应再运行待处理作业,请在stop_service()中使用remove_plugin_once_job()取消。 - 仪表板:使用
get_dashboard_meta()和get_dashboard()提供主页小组件。 - 工作流操作:使用
get_actions();操作函数首先接收ActionContent,并返回(success, action_content)。 - Agent 工具:使用
get_agent_tools();每个工具类都必须继承app.agent.tools.base.MoviePilotTool。 - 自定义 Vue UI:选用 Vue UI 模式时,实现
get_render_mode()。返回("vue", "<compiled-assets-path>"),并将构建后的前端资源放入插件目录。 - Agent 助手头像。若要替换网页助手的屏幕角色,请在 Vue 插件上实现
get_agent_pets()。参见下方“Agent 助手头像”。
Vue 联邦 UI
若在预检 UI 决策中选用 Vue 联邦,则使用 Vue 联邦。 Vue 插件的后端方法、构建文件和联邦 exposes 必须保持一致。
后端要求:
from typing import Any, Dict, List, Tuple
@staticmethod
def get_render_mode() -> Tuple[str, str]:
"""声明插件使用 Vue 联邦组件渲染。"""
return "vue", "dist/assets"
def get_form(self) -> Tuple[List[dict], Dict[str, Any]]:
"""Vue 模式下返回默认配置模型。"""
return [], self._current_config()
def get_page(self) -> List[dict]:
"""Vue 模式下详情页由远程 Page 组件渲染。"""
return []
如果插件需要主布局侧边栏页面,还需实现:
def get_sidebar_nav(self) -> List[Dict[str, Any]]:
"""声明插件在主界面左侧导航栏中的全页入口。"""
if not self.get_state():
return []
return [
{
"nav_key": "main",
"title": "我的插件",
"icon": "mdi-puzzle",
"section": "system",
"permission": "manage",
"order": 10,
}
]
侧边栏规则:
- 仅聚合已启用且
get_render_mode()返回"vue"的插件的侧边栏条目。 section必须是start、discovery、subscribe、organize、system之一;无效值将回退为system。permission可以是subscribe、discovery、search、manage或admin;无效值将被忽略。nav_key默认为main,且不得包含/、?、#或空格。- 允许有多个侧边栏条目;每个条目都需要稳定的
nav_key。
如果插件提供 Agent 助手头像(助手形象),还需实现 get_agent_pets()。参见下方“Agent 助手头像”。
前端联邦要求:
federation({
name: 'MyPlugin',
filename: 'remoteEntry.js',
exposes: {
'./Page': './src/components/Page.vue',
'./Config': './src/components/Config.vue',
'./Dashboard': './src/components/Dashboard.vue',
'./AppPage': './src/components/AppPage.vue',
'./AppPageSettings': './src/components/AppPageSettings.vue',
},
shared: {
vue: { requiredVersion: false, generate: false },
vuetify: { requiredVersion: false, generate: false, singleton: true },
'vuetify/styles': { requiredVersion: false, generate: false, singleton: true },
},
format: 'esm',
})
构建要求:
- 将 Vite 的
build.target设为esnext,因为联邦使用顶层 await。 - 使用
cssCodeSplit: true,并尽可能使用作用域样式或组件局部样式。 - 使用前端项目文档中说明的命令进行构建,然后将
remoteEntry.js及其引用的所有 JS/CSS/资源文件保留在dist/assets下。 - 不要将前端运行时依赖添加到插件 Python 的
requirements.txt;请将前端依赖保留在前端构建项目中。
组件约定:
Page渲染插件详情对话框,并可发出action、switch和close事件。Config渲染插件设置,接收initialConfig和api,并发出save、close和switch事件。Dashboard接收config和allowRefresh。AppPage渲染主布局侧边栏页面,并接收api、pluginId和navKey。- 对于侧边栏
nav_key=main,前端依次加载./AppPage和./Page。 - 对于其他任何
nav_key,前端依次加载./AppPage{PascalCase(nav_key)}、./AppPage和./Page。示例:settings -> AppPageSettings、my_tool -> AppPageMyTool。 - 单个
AppPage可以根据navKey分支处理,也可以为特定条目提供单独的AppPage{PascalCase}文件。
Agent 助手形象:
def get_agent_pets(self) -> List[Dict[str, Any]]:
"""声明插件提供的 Agent 助手形象。"""
return [
{
"key": "girl",
"name": "看板娘",
"mode": "stage",
"component": "AgentPet",
"preview": "girl-preview.png",
"avatar": "girl-avatar.png",
}
]
- 仅对已启用且
get_render_mode()返回"vue"的插件,通过GET /api/v1/plugin/agent_pets聚合声明。 - 必填字段为
key([a-z0-9_-]{1,32},在插件内唯一)和name。 可选字段包括description、mode(默认为renderer,也可以是stage)、component(联邦暴露名称,默认为AgentPet)、api_version(默认为1)、preview、avatar、bubbles(仅用于 stage,可设为host或self)以及random_actions(仅用于 renderer)。 preview和avatar可以是相对于remoteEntry.js所在目录(即get_render_mode()返回的目录)的路径,也可以是http(s):///data:URL。 使用("vue", "dist/assets")时,将girl-preview.png放在dist/assets/remoteEntry.js旁边,并声明"girl-preview.png"。写成"assets/girl-preview.png"会解析为dist/assets/assets/girl-preview.png。 相对路径不得包含..、\或:。- 含有任何无效字段的条目都会被丢弃并发出警告;重复的
key则保留第一个条目。 一个插件可以声明多个形象,每个形象都可以有自己的mode和component, 并且必须暴露所有被引用的组件。 - 形象仅负责渲染角色。它不得接管 Agent 面板或发送消息,并且
agent.open({ draft })只会填充输入框。 - 用户可在 Agent 面板标题栏中点击“更换形象”按钮选择形象;管理员默认值为
AI_AGENT_PET(<plugin_id>:<key>)。加载失败、超时超过 8 秒以及运行时错误 都会回退到内置机器人。 - 请阅读
MoviePilot-Frontend/docs/module-federation-guide.md的 “Agent 助手形象(AgentPet)”章节(5.11),了解组件 props、moviepilot:agent宿主 API、agent.*事件、renderer 的 action/intent 映射、16KB 的pet.storage限制以及 stage 示例。
Vue API 调用:
- 使用
auth: "bear"定义面向前端的插件 API。 - 使用注入的 API 对象调用这些 API,例如
props.api.get(\plugin/${props.pluginId}/history`)`。 - 不要将
settings.API_TOKEN传入 Vue 组件用于浏览器端调用。
本地安装和重新加载
仅在授权的安装/运行时范围内执行此阶段。
- 在已配置的本地插件仓库中写入文件后,调用
moviepilot_api, 并使用operation_id=plugin.market和查询字段query="<PluginID>"、force_refresh=true,确认本地来源可见。 - 使用
operation_id=plugin.install、路径参数plugin_id="<PluginID>"和query.force=true安装或重新安装。 安装流程会将来源复制到app/plugins/<plugin_id_lower>/。 - 如果启用了
PLUGIN_AUTO_RELOAD或开发模式,已安装本地插件中的 Python 源代码更改 可以自动同步并重新加载。如果未启用,则在编辑运行时文件后,使用路径参数plugin_id调用operation_id=plugin.reload。 - 依赖清单更改时,使用
force=True重新安装;仅重新加载不会安装新依赖。
验证
- 重新阅读已更改的文件,确认类名、目录名、包 ID 和包版本保持一致。
- 确认每个公共类、公共方法和公共函数都有中文文档字符串。
- 确认每个新编写的函数或方法都有中文文档字符串,即使它是私有辅助代码。
- 对于 Vue 联邦插件,确认
get_render_mode()返回("vue", "dist/assets")或实际构建的资源路径,并且dist/assets/remoteEntry.js存在。 - 对于侧边栏插件,确认插件已启用,
get_state()返回True,get_sidebar_nav()返回有效条目,并且所有非主导航nav_key都有对应的AppPageexpose,或由通用AppPage处理。 - 确认面向前端的 API 路由使用
auth: "bear",且浏览器代码通过提供的api属性调用这些路由。 - 通过 MoviePilot 工具发起外部 HTTP 调用,并避免在测试中进行真实网络调用。
- 如果插件包含非简单逻辑,使用仓库针对目标代际的引导机制,添加或更新 pytest 原生测试。检查当前测试配置,不要将 V2 后端或命名空间复制到 V3 中。
- 对已修改的部分运行范围最小的验证。在此仓库中,请遵循
docs/rules/03-commands.md;对于仅包含插件的仓库,请遵循其自身记录的验证命令。 - 对于插件仓库中的 Python 更改,尽可能使用主机 Python 环境,并至少对已修改的插件文件运行语法编译。
- 对于 Vue 联邦更改,在可用时运行前端项目记录的类型检查和构建命令,然后确认构建资源已复制到插件目录。
Vue 联邦故障排查
GET /api/v1/plugin/remotes?token=moviepilot应包含该插件,且 URL 以/plugin/file/<plugin_id_lower>/<dist_path>/remoteEntry.js结尾。GET /api/v1/plugin/sidebar_nav应包含已启用的 Vue 插件的侧边栏条目,且这些条目的nav_key、section和permission均有效。- 如果控制台提示
Module name 'vue' does not resolve to a valid URL,请检查联邦shared配置,并使用requiredVersion: false。 - 如果控制台提示不支持顶层 await,请将
build.target设置为esnext。 - 如果动态导入失败,请检查远程文件请求状态、计算出的
remoteEntry.js路径,以及已安装的运行时插件目录中是否实际包含构建资源。 - 如果侧边栏页面空白,请检查当前
nav_key的 expose 名称解析和回退机制(AppPage{PascalCase}->AppPage->Page)。
最终报告
报告以下内容:
- 插件 ID、源路径,以及已安装时的运行时路径。
- 主机/插件代际以及已更改的包文件(
package.v3.json、package.v2.json或旧版package.json)。 - 使用的 UI 模式(
vuetifyJSON 或vue联邦);对于 Vue 插件,还需报告 expose 的组件和构建资源路径。 - 插件是否已安装或重新加载。
- 运行的验证命令,或未运行验证的原因。