mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-11-04 14:24:58 +08:00
[功能] publish-moviepilot-plugin 技能支持 V3 插件仓库布局 (#7163)
This commit is contained in:
@@ -1,11 +1,12 @@
|
||||
---
|
||||
name: publish-moviepilot-plugin
|
||||
version: 4
|
||||
version: 5
|
||||
description: >-
|
||||
Use this skill when the user asks to publish, upload, sync, pull, push, diff,
|
||||
or maintain a MoviePilot local plugin in a GitHub repository. Covers using the
|
||||
configured MoviePilot GitHub token, PLUGIN_LOCAL_REPO_PATHS local plugin
|
||||
repositories, package.json/package.v2.json metadata, plugins/plugins.v2
|
||||
repositories, package.json/package.v2.json/package.v3.json metadata,
|
||||
plugins/plugins.v2/plugins.v3
|
||||
layouts, safe file exclusion, diff preview before publishing, incremental
|
||||
GitHub Contents API updates, and syncing local plugin changes back from GitHub.
|
||||
Includes asking whether to use an existing repository or create a new public
|
||||
@@ -23,9 +24,11 @@ through GitHub while protecting local secrets and unrelated plugins.
|
||||
|
||||
## Scope
|
||||
|
||||
- Publish one local plugin under `plugins.v2/<plugin_id_lower>/` or
|
||||
`plugins/<plugin_id_lower>/` to a GitHub repository.
|
||||
- Merge only that plugin's entry into `package.v2.json` or `package.json`.
|
||||
- Publish one local plugin under `plugins.v3/<plugin_id_lower>/`,
|
||||
`plugins.v2/<plugin_id_lower>/` or `plugins/<plugin_id_lower>/` to a GitHub
|
||||
repository.
|
||||
- Merge only that plugin's entry into `package.v3.json`, `package.v2.json` or
|
||||
`package.json`.
|
||||
- Preview local/remote differences before writing.
|
||||
- Pull remote plugin files back to the local plugin source.
|
||||
- Create the target GitHub repository when the user explicitly chooses automatic
|
||||
@@ -41,6 +44,7 @@ through GitHub while protecting local secrets and unrelated plugins.
|
||||
- GitHub token settings: `app/runtime/config.py`, especially `GITHUB_TOKEN` and
|
||||
`REPO_GITHUB_TOKEN`.
|
||||
- Plugin package layouts:
|
||||
- V3: `package.v3.json` and `plugins.v3/<plugin_id_lower>/`
|
||||
- V2: `package.v2.json` and `plugins.v2/<plugin_id_lower>/`
|
||||
- Legacy: `package.json` and `plugins/<plugin_id_lower>/`
|
||||
|
||||
@@ -61,6 +65,8 @@ through GitHub while protecting local secrets and unrelated plugins.
|
||||
and state that the repository will be public by default.
|
||||
- Do not create a private repository unless the user explicitly asks for it.
|
||||
3. Select the package version layout.
|
||||
- Prefer `v3` when the plugin lives under `plugins.v3/<plugin_id_lower>/`
|
||||
or the repository ships `package.v3.json` (current MoviePilot V3 hosts).
|
||||
- Prefer `v2` when `package.v2.json` or `plugins.v2/<plugin_id_lower>/`
|
||||
exists.
|
||||
- Use legacy only when the local plugin is under `plugins/`.
|
||||
@@ -94,12 +100,25 @@ python skills/publish-moviepilot-plugin/scripts/publish_plugin.py pull \
|
||||
--local-repo /path/to/MoviePilot-Plugins \
|
||||
--package-version v2
|
||||
|
||||
# V3 插件仓库(package.v3.json + plugins.v3/)
|
||||
python skills/publish-moviepilot-plugin/scripts/publish_plugin.py push \
|
||||
--repo owner/repo \
|
||||
--plugin-id MyPlugin \
|
||||
--local-repo /path/to/MoviePilot-Plugins \
|
||||
--package-version v3 \
|
||||
--message "Publish MyPlugin v1.0.0"
|
||||
|
||||
python skills/publish-moviepilot-plugin/scripts/publish_plugin.py create-repo \
|
||||
--repo owner/repo
|
||||
```
|
||||
|
||||
Options:
|
||||
|
||||
- `--package-version v3|v2|legacy|auto`: layout selector. `auto` resolves the
|
||||
layout from where the plugin directory actually lives
|
||||
(`plugins.v3` → `plugins.v2` → `plugins`), then falls back to the manifest
|
||||
files, so repositories that still keep both manifests stay correct. Pass
|
||||
`v3` explicitly for current MoviePilot V3 plugin repositories.
|
||||
- `create-repo`: create the target GitHub repository. Default visibility is
|
||||
public; use `--private` only when the user explicitly asked for private.
|
||||
- `preview`: compare local filtered files with remote files and print JSON.
|
||||
@@ -115,6 +134,20 @@ Options:
|
||||
- `--dry-run`: print planned changes without writing.
|
||||
- `--proxy URL`: use an explicit HTTP/HTTPS proxy for GitHub API requests.
|
||||
|
||||
## GitHub Release 与 README
|
||||
|
||||
`push` 只同步插件源码与 package 条目,**不会创建 GitHub Release,也不会修改
|
||||
README**。目标仓库若用 GitHub Actions 打包(例如
|
||||
`.github/workflows/plugins-release.yml`,产物为 `Plugin_vX.Y.Z` tag 与
|
||||
zip/tar.gz + sha256 资产):
|
||||
|
||||
- package 条目必须显式写 `"release": true`,否则工作流即使运行也不会发版。
|
||||
- 自动触发依赖**同一次 push 中 package 文件的 version 发生变化**。逐文件提交时
|
||||
`package.v3.json` 的变更可能落在另一个提交,diff 里看不到它,于是静默跳过;
|
||||
这种情况改用 `workflow_dispatch`(`source_directory` + `plugin_id`)手动触发。
|
||||
- README 的插件表格与说明需要单独编辑并推送(Contents API 或 git),不在
|
||||
`push` 的职责范围内。
|
||||
|
||||
## Safety Rules
|
||||
|
||||
- Always run `preview` before `push` unless the user explicitly asks for a
|
||||
|
||||
@@ -50,6 +50,7 @@ PACKAGE_BY_VERSION = {
|
||||
"legacy": ("package.json", "plugins"),
|
||||
"v1": ("package.json", "plugins"),
|
||||
"v2": ("package.v2.json", "plugins.v2"),
|
||||
"v3": ("package.v3.json", "plugins.v3"),
|
||||
}
|
||||
TOKEN_ENV_NAMES = ("MOVIEPILOT_GITHUB_TOKEN", "GITHUB_TOKEN", "GH_TOKEN")
|
||||
DEFAULT_EXCLUDES = (
|
||||
@@ -167,6 +168,8 @@ class Layout:
|
||||
"""
|
||||
返回布局版本标识。
|
||||
"""
|
||||
if self.plugin_root == "plugins.v3":
|
||||
return "v3"
|
||||
if self.plugin_root == "plugins.v2":
|
||||
return "v2"
|
||||
return "legacy"
|
||||
@@ -714,7 +717,7 @@ def resolve_layout(package_version: str, local_repo: Path, plugin_id: str) -> La
|
||||
"""
|
||||
根据参数和本地文件推断插件仓库布局。
|
||||
|
||||
:param package_version: v2、legacy 或 auto
|
||||
:param package_version: v3、v2、legacy 或 auto
|
||||
:param local_repo: 本地仓库目录
|
||||
:param plugin_id: 插件 ID
|
||||
:return: 布局描述
|
||||
@@ -723,11 +726,22 @@ def resolve_layout(package_version: str, local_repo: Path, plugin_id: str) -> La
|
||||
if normalized in PACKAGE_BY_VERSION:
|
||||
package_file, plugin_root = PACKAGE_BY_VERSION[normalized]
|
||||
return Layout(package_file=package_file, plugin_root=plugin_root)
|
||||
v3_dir = local_repo / "plugins.v3" / plugin_id.lower()
|
||||
v2_dir = local_repo / "plugins.v2" / plugin_id.lower()
|
||||
legacy_dir = local_repo / "plugins" / plugin_id.lower()
|
||||
if (local_repo / "package.v2.json").exists() or v2_dir.exists():
|
||||
# 先看插件实际落在哪个目录:同一仓库可能同时保留 v2 与 v3 清单,
|
||||
# 只看清单文件会把只存在于 plugins.v2 的插件误判成 v3 布局。
|
||||
if v3_dir.exists():
|
||||
return Layout(package_file="package.v3.json", plugin_root="plugins.v3")
|
||||
if v2_dir.exists():
|
||||
return Layout(package_file="package.v2.json", plugin_root="plugins.v2")
|
||||
if (local_repo / "package.json").exists() or legacy_dir.exists():
|
||||
if legacy_dir.exists():
|
||||
return Layout(package_file="package.json", plugin_root="plugins")
|
||||
if (local_repo / "package.v3.json").exists():
|
||||
return Layout(package_file="package.v3.json", plugin_root="plugins.v3")
|
||||
if (local_repo / "package.v2.json").exists():
|
||||
return Layout(package_file="package.v2.json", plugin_root="plugins.v2")
|
||||
if (local_repo / "package.json").exists():
|
||||
return Layout(package_file="package.json", plugin_root="plugins")
|
||||
return Layout(package_file="package.v2.json", plugin_root="plugins.v2")
|
||||
|
||||
@@ -1328,7 +1342,7 @@ def build_parser() -> argparse.ArgumentParser:
|
||||
parser.add_argument("--repo", required=True, help="GitHub 仓库,格式 owner/repo")
|
||||
parser.add_argument("--plugin-id", default="", help="插件类名 ID")
|
||||
parser.add_argument("--local-repo", default="", help="本地插件仓库目录")
|
||||
parser.add_argument("--package-version", default="auto", help="auto、v2 或 legacy")
|
||||
parser.add_argument("--package-version", default="auto", help="auto、v3、v2 或 legacy")
|
||||
parser.add_argument("--branch", default=DEFAULT_BRANCH, help="目标分支")
|
||||
parser.add_argument("--token", default="", help="GitHub token,默认读取配置或环境变量")
|
||||
parser.add_argument("--message", default="", help="提交消息")
|
||||
|
||||
@@ -137,7 +137,7 @@ def test_modified_builtin_skills_have_incremented_versions() -> None:
|
||||
"generate-identifiers": "4",
|
||||
"create-moviepilot-plugin": "7",
|
||||
"create-moviepilot-skill": "3",
|
||||
"publish-moviepilot-plugin": "4",
|
||||
"publish-moviepilot-plugin": "5",
|
||||
"downloader-operation": "3",
|
||||
"mediaserver-operation": "3",
|
||||
}
|
||||
|
||||
@@ -261,3 +261,33 @@ def test_create_repo_dry_run_does_not_touch_github() -> None:
|
||||
assert payload["success"] is True
|
||||
assert payload["dry_run"] is True
|
||||
assert payload["private"] is False
|
||||
|
||||
|
||||
def test_resolve_layout_supports_v3_repository(tmp_path: Path) -> None:
|
||||
"""
|
||||
V3 仓库应被 auto 识别为 package.v3.json 与 plugins.v3。
|
||||
"""
|
||||
module = load_publish_plugin_module()
|
||||
(tmp_path / "plugins.v3" / "myplugin").mkdir(parents=True)
|
||||
(tmp_path / "package.v3.json").write_text("{}", encoding="utf-8")
|
||||
|
||||
layout = module.resolve_layout("auto", tmp_path, "MyPlugin")
|
||||
|
||||
assert layout.package_file == "package.v3.json"
|
||||
assert layout.plugin_root == "plugins.v3"
|
||||
assert layout.version == "v3"
|
||||
|
||||
|
||||
def test_resolve_layout_prefers_plugin_directory_over_manifest(tmp_path: Path) -> None:
|
||||
"""
|
||||
同一仓库同时存在 v3 清单时,仍应按插件实际所在目录判定布局。
|
||||
"""
|
||||
module = load_publish_plugin_module()
|
||||
(tmp_path / "plugins.v2" / "myplugin").mkdir(parents=True)
|
||||
(tmp_path / "package.v3.json").write_text("{}", encoding="utf-8")
|
||||
(tmp_path / "package.v2.json").write_text("{}", encoding="utf-8")
|
||||
|
||||
layout = module.resolve_layout("auto", tmp_path, "MyPlugin")
|
||||
|
||||
assert layout.package_file == "package.v2.json"
|
||||
assert layout.plugin_root == "plugins.v2"
|
||||
|
||||
Reference in New Issue
Block a user