Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/ISSUE_TEMPLATE/external-plugin.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 0 additions & 1 deletion .github/plugin/marketplace.json
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,6 @@
"source": {
"source": "github",
"repo": "ChromeDevTools/chrome-devtools-mcp",
"path": ".github/plugin/plugin.json",
"ref": "chrome-devtools-mcp-v1.0.1"
}
},
Expand Down Expand Up @@ -584,7 +583,7 @@
"description": "MCP server and 12 skills for product analysis, specification writing, strategy, narrative, and stakeholder alignment. Run competitive analyses, write zero-question specs, design metrics, and produce executive-ready documents directly from Copilot.",
"version": "2.1.0",
"author": {
"name": "Parth Sangani",

Check failure on line 586 in .github/plugin/marketplace.json

View workflow job for this annotation

GitHub Actions / codespell

Parth ==> Path
"url": "https://github.com/Avyayalaya"
},
"repository": "https://github.com/Avyayalaya/pm-skills-arsenal",
Expand Down
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`.

Expand Down
24 changes: 24 additions & 0 deletions eng/external-plugin-validation.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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("/");

Expand All @@ -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) {
Expand Down
25 changes: 3 additions & 22 deletions plugins/external.json
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,6 @@
"source": {
"source": "github",
"repo": "ChromeDevTools/chrome-devtools-mcp",
"path": ".github/plugin/plugin.json",
"ref": "chrome-devtools-mcp-v1.0.1"
}
},
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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": {
Expand All @@ -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": {
Expand All @@ -315,7 +296,7 @@
"description": "MCP server and 12 skills for product analysis, specification writing, strategy, narrative, and stakeholder alignment. Run competitive analyses, write zero-question specs, design metrics, and produce executive-ready documents directly from Copilot.",
"version": "2.1.0",
"author": {
"name": "Parth Sangani",

Check failure on line 299 in plugins/external.json

View workflow job for this annotation

GitHub Actions / codespell

Parth ==> Path
"url": "https://github.com/Avyayalaya"
},
"repository": "https://github.com/Avyayalaya/pm-skills-arsenal",
Expand Down
2 changes: 1 addition & 1 deletion website/src/scripts/modal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
2 changes: 1 addition & 1 deletion website/src/scripts/pages/plugins-render.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ export function sortPlugins<T extends RenderablePlugin>(
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);
Expand Down
Loading