mirror of
https://github.com/github/awesome-copilot.git
synced 2026-08-14 05:06:54 +00:00
fix(website): resolve extension owner plugin
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 31016278-f98b-48ef-8a77-430091b34b91
This commit is contained in:
@@ -0,0 +1,81 @@
|
|||||||
|
import fs from "fs";
|
||||||
|
import path from "path";
|
||||||
|
|
||||||
|
const AWESOME_COPILOT_NAMESPACE = "com.github.awesome-copilot";
|
||||||
|
|
||||||
|
function extensionIdFromReference(reference) {
|
||||||
|
if (typeof reference !== "string" || !reference.startsWith("./extensions/")) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return reference.replace(/^\.\/extensions\//, "").replace(/\/$/, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
export function buildExtensionPluginOwners(pluginEntries) {
|
||||||
|
const owners = new Map();
|
||||||
|
const sortedEntries = [...pluginEntries].sort((a, b) =>
|
||||||
|
a.directoryName.localeCompare(b.directoryName)
|
||||||
|
);
|
||||||
|
|
||||||
|
for (const { directoryName, manifest } of sortedEntries) {
|
||||||
|
const pluginName =
|
||||||
|
typeof manifest?.name === "string" && manifest.name.trim()
|
||||||
|
? manifest.name.trim()
|
||||||
|
: directoryName;
|
||||||
|
const extensionIds = new Set([directoryName]);
|
||||||
|
const references =
|
||||||
|
manifest?.extensions?.[AWESOME_COPILOT_NAMESPACE]?.extensions;
|
||||||
|
|
||||||
|
if (Array.isArray(references)) {
|
||||||
|
for (const reference of references) {
|
||||||
|
const extensionId = extensionIdFromReference(reference);
|
||||||
|
if (extensionId) {
|
||||||
|
extensionIds.add(extensionId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const extensionId of extensionIds) {
|
||||||
|
const pluginNames = owners.get(extensionId) ?? [];
|
||||||
|
if (!pluginNames.includes(pluginName)) {
|
||||||
|
pluginNames.push(pluginName);
|
||||||
|
}
|
||||||
|
owners.set(extensionId, pluginNames);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return owners;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function readExtensionPluginOwners(pluginsDir) {
|
||||||
|
if (!fs.existsSync(pluginsDir)) {
|
||||||
|
return new Map();
|
||||||
|
}
|
||||||
|
|
||||||
|
const pluginEntries = fs
|
||||||
|
.readdirSync(pluginsDir, { withFileTypes: true })
|
||||||
|
.filter((entry) => entry.isDirectory())
|
||||||
|
.map((entry) => {
|
||||||
|
const manifestPath = path.join(pluginsDir, entry.name, "plugin.json");
|
||||||
|
if (!fs.existsSync(manifestPath)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
directoryName: entry.name,
|
||||||
|
manifest: JSON.parse(fs.readFileSync(manifestPath, "utf-8")),
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.filter(Boolean);
|
||||||
|
|
||||||
|
return buildExtensionPluginOwners(pluginEntries);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function resolveExtensionPluginName(extensionId, owners) {
|
||||||
|
const pluginNames = owners.get(extensionId) ?? [];
|
||||||
|
return (
|
||||||
|
pluginNames.find((pluginName) => pluginName === extensionId) ??
|
||||||
|
pluginNames[0] ??
|
||||||
|
extensionId
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
import assert from "node:assert/strict";
|
||||||
|
import { test } from "node:test";
|
||||||
|
import {
|
||||||
|
buildExtensionPluginOwners,
|
||||||
|
resolveExtensionPluginName,
|
||||||
|
} from "./extension-plugin-ownership.mjs";
|
||||||
|
|
||||||
|
const namespace = "com.github.awesome-copilot";
|
||||||
|
|
||||||
|
test("resolves an extension bundled only by its parent plugin", () => {
|
||||||
|
const owners = buildExtensionPluginOwners([
|
||||||
|
{
|
||||||
|
directoryName: "ember",
|
||||||
|
manifest: {
|
||||||
|
name: "ember",
|
||||||
|
extensions: {
|
||||||
|
[namespace]: {
|
||||||
|
extensions: ["./extensions/daily-focus-board"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
assert.equal(
|
||||||
|
resolveExtensionPluginName("daily-focus-board", owners),
|
||||||
|
"ember"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("prefers a same-named standalone plugin over another owner", () => {
|
||||||
|
const owners = buildExtensionPluginOwners([
|
||||||
|
{
|
||||||
|
directoryName: "parent-plugin",
|
||||||
|
manifest: {
|
||||||
|
name: "parent-plugin",
|
||||||
|
extensions: {
|
||||||
|
[namespace]: {
|
||||||
|
extensions: ["./extensions/daily-focus-board/"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
directoryName: "daily-focus-board",
|
||||||
|
manifest: { name: "daily-focus-board" },
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
assert.equal(
|
||||||
|
resolveExtensionPluginName("daily-focus-board", owners),
|
||||||
|
"daily-focus-board"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("preserves the extension name when no plugin owns it", () => {
|
||||||
|
assert.equal(
|
||||||
|
resolveExtensionPluginName("daily-focus-board", new Map()),
|
||||||
|
"daily-focus-board"
|
||||||
|
);
|
||||||
|
});
|
||||||
@@ -26,6 +26,10 @@ import {
|
|||||||
parseYamlFile,
|
parseYamlFile,
|
||||||
} from "./yaml-parser.mjs";
|
} from "./yaml-parser.mjs";
|
||||||
import { readExternalPlugins } from "./external-plugin-validation.mjs";
|
import { readExternalPlugins } from "./external-plugin-validation.mjs";
|
||||||
|
import {
|
||||||
|
readExtensionPluginOwners,
|
||||||
|
resolveExtensionPluginName,
|
||||||
|
} from "./extension-plugin-ownership.mjs";
|
||||||
|
|
||||||
const __filename = fileURLToPath(import.meta.url);
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
|
|
||||||
@@ -1177,6 +1181,7 @@ function generateCanvasManifest(gitDates, commitSha) {
|
|||||||
return { items: [], filters: { keywords: [] } };
|
return { items: [], filters: { keywords: [] } };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const extensionPluginOwners = readExtensionPluginOwners(PLUGINS_DIR);
|
||||||
const extensionDirs = fs
|
const extensionDirs = fs
|
||||||
.readdirSync(EXTENSIONS_DIR, { withFileTypes: true })
|
.readdirSync(EXTENSIONS_DIR, { withFileTypes: true })
|
||||||
.filter((entry) => {
|
.filter((entry) => {
|
||||||
@@ -1212,6 +1217,7 @@ function generateCanvasManifest(gitDates, commitSha) {
|
|||||||
normalizeText(packageJson.description, "Canvas extension")
|
normalizeText(packageJson.description, "Canvas extension")
|
||||||
);
|
);
|
||||||
const extensionName = normalizeText(pluginJson.name, normalizeText(packageJson.name, dir.name));
|
const extensionName = normalizeText(pluginJson.name, normalizeText(packageJson.name, dir.name));
|
||||||
|
const pluginName = resolveExtensionPluginName(dir.name, extensionPluginOwners);
|
||||||
const extensionVersion = normalizeText(pluginJson.version, normalizeText(packageJson.version, "1.0.0"));
|
const extensionVersion = normalizeText(pluginJson.version, normalizeText(packageJson.version, "1.0.0"));
|
||||||
const readmeFile = fs.existsSync(path.join(extensionDir, "README.md"))
|
const readmeFile = fs.existsSync(path.join(extensionDir, "README.md"))
|
||||||
? `${relPath}/README.md`
|
? `${relPath}/README.md`
|
||||||
@@ -1230,7 +1236,7 @@ function generateCanvasManifest(gitDates, commitSha) {
|
|||||||
/\\/g,
|
/\\/g,
|
||||||
"/"
|
"/"
|
||||||
)}`;
|
)}`;
|
||||||
const installCommand = `copilot plugin install ${extensionName}@awesome-copilot`;
|
const installCommand = `copilot plugin install ${pluginName}@awesome-copilot`;
|
||||||
|
|
||||||
for (const canvas of canvasEntries) {
|
for (const canvas of canvasEntries) {
|
||||||
const canvasId = normalizeText(canvas.id, dir.name);
|
const canvasId = normalizeText(canvas.id, dir.name);
|
||||||
@@ -1241,7 +1247,7 @@ function generateCanvasManifest(gitDates, commitSha) {
|
|||||||
canvasId,
|
canvasId,
|
||||||
extensionId: dir.name,
|
extensionId: dir.name,
|
||||||
extensionName,
|
extensionName,
|
||||||
pluginName: extensionName,
|
pluginName,
|
||||||
name: canvasName,
|
name: canvasName,
|
||||||
version: extensionVersion,
|
version: extensionVersion,
|
||||||
readmeFile,
|
readmeFile,
|
||||||
|
|||||||
Reference in New Issue
Block a user