Migrate extension metadata to plugin.json and enforce conventions (#2177)

* Remove pluginRoots property from marketplace.json

The pluginRoots property is not used by install tooling and was only
informational about the extension/plugin source directories. Removing it
simplifies the marketplace.json structure while maintaining all functionality.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

* Migrate java-modernization-studio to plugin.json and update validation workflow

- Create .github/plugin/plugin.json for java-modernization-studio extension
- Remove legacy canvas.json from java-modernization-studio
- Update validate-canvas-extensions.yml workflow to check for plugin.json instead of canvas.json
- Update workflow to trigger on .schemas/plugin.schema.json changes (instead of canvas.schema.json)
- Remove schema validation logic that relied on canvas.schema.json
- All 12 extensions now use plugin.json for metadata

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

* Add extensions field to all extension plugin.json files

Per https://github.com/github/copilot-agent-runtime/pull/9929, plugins that ship
extensions need to include an extensions field specifying where the extension code
is located. All 12 extensions now have extensions set to '.' to reference the
current directory where extension.mjs is located.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

* Enforce convention-based extension metadata and remove x-awesome-copilot

- Remove x-awesome-copilot.screenshots from all extension plugin.json files
- Enforce logo=assets/preview.png convention for all extensions
- Enforce extensions=. per copilot-agent-runtime#9929
- Update validate-plugins.mjs to enforce conventions
- Update validate-canvas-extensions.yml workflow with convention checks
- Update AGENTS.md and CONTRIBUTING.md documentation

All 12 extensions validated successfully.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

* Potential fix for pull request finding

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

* Use standard plugin validation for extensions

Remove the custom extension schema and schema validation helper, and
validate extension plugin.json files through the existing plugin validator
instead. Update workflows to stop depending on the removed schema.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
Aaron Powell
2026-07-03 12:24:38 +10:00
committed by GitHub
parent da3855ab20
commit e986f49695
27 changed files with 748 additions and 687 deletions
+2 -2
View File
@@ -18,8 +18,8 @@ const initialItems = sortExtensions(extensionsData.items, 'title');
<div class="page-content">
<div class="container">
<div class="how-to-install">
<p><strong>Installing in the GitHub Copilot app:</strong> Click <strong>Copy URL</strong>, then ask Copilot to install that extension URL.</p>
<p><strong>Example:</strong> <code>Install this extension: https://github.com/github/awesome-copilot/tree/main/extensions/accessibility-kanban</code></p>
<p><strong>Installing in the GitHub Copilot app:</strong> Click <strong>Copy Install</strong>, then run that plugin install command in Copilot.</p>
<p><strong>Example:</strong> <code>copilot plugin install accessibility-kanban@awesome-copilot</code></p>
</div>
<div class="listing-toolbar">
+14 -1
View File
@@ -37,6 +37,8 @@ export interface RenderableExtension {
} | null;
imageUrl?: string | null;
assetPath?: string | null;
pluginName?: string | null;
installCommand?: string | null;
installUrl?: string | null;
sourceUrl?: string | null;
external?: boolean;
@@ -78,6 +80,9 @@ export function renderExtensionsHtml(items: RenderableExtension[]): string {
"/"
)}`
: "");
const installCommand =
item.installCommand ||
(item.pluginName ? `copilot plugin install ${item.pluginName}@awesome-copilot` : "");
const sourceUrl =
item.sourceUrl || (item.path ? getGitHubUrl(item.path) : "");
@@ -126,8 +131,16 @@ export function renderExtensionsHtml(items: RenderableExtension[]): string {
const actionsHtml = `
<button
class="btn btn-primary btn-small copy-install-url-btn"
data-install-command="${escapeHtml(installCommand)}"
title="Copy plugin install command"
${installCommand ? "" : "disabled"}
>
Copy Install
</button>
<button
class="btn btn-secondary btn-small copy-install-url-btn"
data-install-url="${escapeHtml(installUrl)}"
title="Copy install URL"
title="Copy fallback URL install target"
${installUrl ? "" : "disabled"}
>
Copy URL
+48 -14
View File
@@ -78,6 +78,13 @@ function getInstallUrl(item: Extension): string {
);
}
function getInstallCommand(item: Extension): string {
return (
item.installCommand ||
(item.pluginName ? `copilot plugin install ${item.pluginName}@awesome-copilot` : "")
);
}
function getSourceUrl(item: Extension): string {
return item.sourceUrl || (item.path ? getGitHubUrl(item.path) : "");
}
@@ -207,6 +214,7 @@ function openDetailsModal(
}
const installUrl = getInstallUrl(item);
const installCommand = getInstallCommand(item);
const sourceUrl = getSourceUrl(item);
const detailsHtml = `
<div class="extension-details-body">
@@ -223,16 +231,18 @@ function openDetailsModal(
""
)}</div>
<div class="resource-actions extension-details-actions">
<button id="extension-details-install" class="btn btn-primary btn-small" type="button" data-install-url="${escapeHtml(
<button id="extension-details-install" class="btn btn-primary btn-small" type="button" data-install-command="${escapeHtml(
installCommand
)}" ${installCommand ? "" : "disabled"}>Copy Install</button>
<button id="extension-details-copy-url" class="btn btn-secondary btn-small" type="button" data-install-url="${escapeHtml(
installUrl
)}" ${installUrl ? "" : "disabled"}>Install</button>
${
sourceUrl
? `<a id="extension-details-source" class="btn btn-secondary btn-small" href="${escapeHtml(
sourceUrl
)}" target="_blank" rel="noopener noreferrer">Source</a>`
: ""
}
)}" ${installUrl ? "" : "disabled"}>Copy URL</button>
${sourceUrl
? `<a id="extension-details-source" class="btn btn-secondary btn-small" href="${escapeHtml(
sourceUrl
)}" target="_blank" rel="noopener noreferrer">Source</a>`
: ""
}
</div>
</div>
</div>
@@ -304,14 +314,20 @@ function setupActionHandlers(list: HTMLElement | null): void {
if (!installButton) return;
event.stopPropagation();
const installCommand = installButton.dataset.installCommand || "";
const installUrl = installButton.dataset.installUrl || "";
if (!installUrl) {
showToast("No install URL available for this extension", "error");
const contentToCopy = installCommand || installUrl;
if (!contentToCopy) {
showToast("No install target available for this extension", "error");
return;
}
const success = await copyToClipboard(installUrl);
const success = await copyToClipboard(contentToCopy);
showToast(
success ? "Extension URL copied!" : "Failed to copy extension URL",
success
? installCommand
? "Install command copied!"
: "Extension URL copied!"
: "Failed to copy install target",
success ? "success" : "error"
);
return;
@@ -371,7 +387,25 @@ function setupActionHandlers(list: HTMLElement | null): void {
"#extension-details-install"
) as HTMLButtonElement | null;
if (detailsInstallButton) {
const installUrl = detailsInstallButton.dataset.installUrl || "";
const installCommand = detailsInstallButton.dataset.installCommand || "";
if (!installCommand) {
showToast("No plugin install command available for this extension", "error");
return;
}
const success = await copyToClipboard(installCommand);
showToast(
success ? "Install command copied!" : "Failed to copy install command",
success ? "success" : "error"
);
return;
}
const detailsCopyUrlButton = target.closest(
"#extension-details-copy-url"
) as HTMLButtonElement | null;
if (detailsCopyUrlButton) {
const installUrl = detailsCopyUrlButton.dataset.installUrl || "";
if (!installUrl) {
showToast("No install URL available for this extension", "error");
return;