diff --git a/skills/publish-moviepilot-plugin/SKILL.md b/skills/publish-moviepilot-plugin/SKILL.md index 9918d6850..804f4ca5d 100644 --- a/skills/publish-moviepilot-plugin/SKILL.md +++ b/skills/publish-moviepilot-plugin/SKILL.md @@ -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//` or - `plugins//` to a GitHub repository. -- Merge only that plugin's entry into `package.v2.json` or `package.json`. +- Publish one local plugin under `plugins.v3//`, + `plugins.v2//` or `plugins//` 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//` - V2: `package.v2.json` and `plugins.v2//` - Legacy: `package.json` and `plugins//` @@ -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//` + or the repository ships `package.v3.json` (current MoviePilot V3 hosts). - Prefer `v2` when `package.v2.json` or `plugins.v2//` 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 diff --git a/skills/publish-moviepilot-plugin/scripts/publish_plugin.py b/skills/publish-moviepilot-plugin/scripts/publish_plugin.py index e7c2bdcd7..1e07c4269 100644 --- a/skills/publish-moviepilot-plugin/scripts/publish_plugin.py +++ b/skills/publish-moviepilot-plugin/scripts/publish_plugin.py @@ -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="提交消息") diff --git a/tests/test_builtin_skill_boundaries.py b/tests/test_builtin_skill_boundaries.py index 9de6ef2a1..db056140e 100644 --- a/tests/test_builtin_skill_boundaries.py +++ b/tests/test_builtin_skill_boundaries.py @@ -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", } diff --git a/tests/test_publish_moviepilot_plugin_skill.py b/tests/test_publish_moviepilot_plugin_skill.py index 2efe2f69e..0eda39c94 100644 --- a/tests/test_publish_moviepilot_plugin_skill.py +++ b/tests/test_publish_moviepilot_plugin_skill.py @@ -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"