From 508da509b17e46d1d342c7a8fae497d1cd233a99 Mon Sep 17 00:00:00 2001 From: Aaron Powell Date: Wed, 20 May 2026 17:24:29 +1000 Subject: [PATCH 1/2] Fixing path on chrome devtools external pluginPath is to the folder in the repo where the plugin structure starts, not where the plugin.json file lives. --- .github/plugin/marketplace.json | 1 - plugins/external.json | 25 +++---------------------- 2 files changed, 3 insertions(+), 23 deletions(-) diff --git a/.github/plugin/marketplace.json b/.github/plugin/marketplace.json index e16417b6f..f61c70a59 100644 --- a/.github/plugin/marketplace.json +++ b/.github/plugin/marketplace.json @@ -126,7 +126,6 @@ "source": { "source": "github", "repo": "ChromeDevTools/chrome-devtools-mcp", - "path": ".github/plugin/plugin.json", "ref": "chrome-devtools-mcp-v1.0.1" } }, diff --git a/plugins/external.json b/plugins/external.json index 184738046..3463d8c2e 100644 --- a/plugins/external.json +++ b/plugins/external.json @@ -73,7 +73,6 @@ "source": { "source": "github", "repo": "ChromeDevTools/chrome-devtools-mcp", - "path": ".github/plugin/plugin.json", "ref": "chrome-devtools-mcp-v1.0.1" } }, @@ -194,13 +193,7 @@ "url": "https://www.figma.com" }, "homepage": "https://github.com/figma/mcp-server-guide", - "keywords": [ - "figma", - "design", - "mcp", - "ui", - "code-connect" - ], + "keywords": ["figma", "design", "mcp", "ui", "code-connect"], "repository": "https://github.com/figma/mcp-server-guide", "source": { "source": "github", @@ -272,14 +265,7 @@ "url": "https://www.microsoft.com" }, "homepage": "https://github.com/microsoft/Build-CLI", - "keywords": [ - "microsoft", - "build", - "ignite", - "events", - "sessions", - "learn" - ], + "keywords": ["microsoft", "build", "ignite", "events", "sessions", "learn"], "license": "Apache-2.0", "repository": "https://github.com/microsoft/Build-CLI", "source": { @@ -296,12 +282,7 @@ "url": "https://www.microsoft.com" }, "homepage": "https://github.com/dotnet/modernize-dotnet", - "keywords": [ - "modernization", - "upgrade", - "migration", - "dotnet" - ], + "keywords": ["modernization", "upgrade", "migration", "dotnet"], "license": "MIT", "repository": "https://github.com/dotnet/modernize-dotnet", "source": { From beb40a9319e9c560b6b008e23ce24c2a5402e015 Mon Sep 17 00:00:00 2001 From: Aaron Powell Date: Thu, 21 May 2026 09:10:35 +1000 Subject: [PATCH 2/2] Updating validation scripts and guidance to avoid this mistake again --- .github/ISSUE_TEMPLATE/external-plugin.yml | 4 ++-- CONTRIBUTING.md | 2 +- eng/external-plugin-validation.mjs | 24 +++++++++++++++++++++ website/src/scripts/modal.ts | 2 +- website/src/scripts/pages/plugins-render.ts | 2 +- 5 files changed, 29 insertions(+), 5 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/external-plugin.yml b/.github/ISSUE_TEMPLATE/external-plugin.yml index 3a0587d5a..ccbb3a777 100644 --- a/.github/ISSUE_TEMPLATE/external-plugin.yml +++ b/.github/ISSUE_TEMPLATE/external-plugin.yml @@ -44,8 +44,8 @@ body: id: plugin-path attributes: label: Plugin path inside the repository - description: Optional if the plugin lives at the repository root. - placeholder: .github/plugins/my-plugin + description: Optional if the plugin lives at the repository root. Otherwise, enter the folder where the plugin structure starts, not the plugin.json file. + placeholder: plugins/my-plugin validations: required: false - type: input diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 0edc60d6b..80e03c975 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -221,7 +221,7 @@ For entries committed to `plugins/external.json`, the current marketplace valida - `repository` as an HTTPS GitHub URL - `keywords` as lowercase hyphenated tags - `source.source: "github"` plus `source.repo` in `owner/repo` format -- optional `source.path` values to stay relative to the repository root +- optional `source.path` values of `/` for repository root, or a repository-relative folder where the plugin structure starts (do not point to `plugin.json` directly) The public-submission policy builds on those rules and also requires `license` plus an immutable `source.ref`. diff --git a/eng/external-plugin-validation.mjs b/eng/external-plugin-validation.mjs index 660c05300..100ce1f4b 100644 --- a/eng/external-plugin-validation.mjs +++ b/eng/external-plugin-validation.mjs @@ -23,6 +23,12 @@ export const EXTERNAL_PLUGIN_POLICIES = Object.freeze({ }), }); +const EXTERNAL_PLUGIN_ROOT_MANIFEST_PATHS = Object.freeze([ + "plugin.json", + ".github/plugin/plugin.json", + ".plugin/plugin.json", +]); + function resolvePolicy(policy) { if (!policy) { return EXTERNAL_PLUGIN_POLICIES.marketplace; @@ -203,12 +209,20 @@ function validateHomepage(homepage, prefix, errors) { validateHttpsUrl(homepage, "homepage", prefix, errors); } +function formatExpectedPluginRootMessage() { + return EXTERNAL_PLUGIN_ROOT_MANIFEST_PATHS.map((manifestPath) => `"${manifestPath}"`).join(", "); +} + function validateRelativePath(pathValue, prefix, errors) { if (!isNonEmptyString(pathValue)) { errors.push(`${prefix}: "source.path" must be a non-empty string when provided`); return; } + if (pathValue === "/") { + return; + } + const normalized = path.posix.normalize(pathValue); const segments = pathValue.split("/"); @@ -219,6 +233,16 @@ function validateRelativePath(pathValue, prefix, errors) { if (pathValue.includes("\\")) { errors.push(`${prefix}: "source.path" must use forward slashes`); } + + if (normalized === ".") { + errors.push(`${prefix}: "source.path" must be "/" for the repository root or a plugin root directory relative to the repository root`); + } + + if (path.posix.basename(normalized) === "plugin.json") { + errors.push( + `${prefix}: "source.path" must point to the plugin root directory, not the manifest file; relative to "source.path", expected one of ${formatExpectedPluginRootMessage()}` + ); + } } function validateImmutableRef(ref, prefix, errors) { diff --git a/website/src/scripts/modal.ts b/website/src/scripts/modal.ts index 95e499c9d..bfdabcdb6 100644 --- a/website/src/scripts/modal.ts +++ b/website/src/scripts/modal.ts @@ -1055,7 +1055,7 @@ async function openPluginModal( function getExternalPluginUrl(plugin: Plugin): string { if (plugin.source?.source === "github" && plugin.source.repo) { const base = `https://github.com/${plugin.source.repo}`; - return plugin.source.path + return plugin.source.path && plugin.source.path !== "/" ? `${base}/tree/main/${plugin.source.path}` : base; } diff --git a/website/src/scripts/pages/plugins-render.ts b/website/src/scripts/pages/plugins-render.ts index b8a13c15f..190a5ce84 100644 --- a/website/src/scripts/pages/plugins-render.ts +++ b/website/src/scripts/pages/plugins-render.ts @@ -49,7 +49,7 @@ export function sortPlugins( function getExternalPluginUrl(plugin: RenderablePlugin): string { if (plugin.source?.source === 'github' && plugin.source.repo) { const base = `https://github.com/${plugin.source.repo}`; - return plugin.source.path ? `${base}/tree/main/${plugin.source.path}` : base; + return plugin.source.path && plugin.source.path !== '/' ? `${base}/tree/main/${plugin.source.path}` : base; } return sanitizeUrl(plugin.repository || plugin.homepage);