mirror of
https://github.com/github/awesome-copilot.git
synced 2026-07-16 10:53:25 +00:00
chore: publish from main
This commit is contained in:
+190
-85
@@ -560,6 +560,7 @@ function getAgentFiles(agentDir, pluginRootPath) {
|
||||
*/
|
||||
function generatePluginsData(gitDates) {
|
||||
const plugins = [];
|
||||
const extensionEntriesByName = new Map();
|
||||
|
||||
if (!fs.existsSync(PLUGINS_DIR)) {
|
||||
return { items: [], filters: { tags: [] } };
|
||||
@@ -569,6 +570,53 @@ function generatePluginsData(gitDates) {
|
||||
.readdirSync(PLUGINS_DIR, { withFileTypes: true })
|
||||
.filter((d) => d.isDirectory());
|
||||
|
||||
if (fs.existsSync(EXTENSIONS_DIR)) {
|
||||
const extensionDirs = fs.readdirSync(EXTENSIONS_DIR, { withFileTypes: true })
|
||||
.filter((entry) => {
|
||||
if (!entry.isDirectory()) return false;
|
||||
return fs.existsSync(path.join(EXTENSIONS_DIR, entry.name, "extension.mjs"));
|
||||
})
|
||||
.map((entry) => entry.name)
|
||||
.sort((a, b) => a.localeCompare(b));
|
||||
|
||||
for (const extensionDirName of extensionDirs) {
|
||||
const extensionDir = path.join(EXTENSIONS_DIR, extensionDirName);
|
||||
const pluginJsonPath = path.join(extensionDir, ".github", "plugin", "plugin.json");
|
||||
if (!fs.existsSync(pluginJsonPath)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
const extensionPlugin = JSON.parse(fs.readFileSync(pluginJsonPath, "utf-8"));
|
||||
const pluginName = normalizeText(extensionPlugin.name, extensionDirName);
|
||||
const pluginDescription = normalizeText(extensionPlugin.description, "Canvas extension");
|
||||
const extensionKeywords = Array.isArray(extensionPlugin.keywords)
|
||||
? [...new Set(extensionPlugin.keywords.filter((keyword) => typeof keyword === "string").map((keyword) => keyword.trim()).filter(Boolean))].sort((a, b) => a.localeCompare(b))
|
||||
: [];
|
||||
const relPath = `extensions/${extensionDirName}`;
|
||||
const extensionItem = {
|
||||
kind: "extension",
|
||||
path: relPath,
|
||||
};
|
||||
|
||||
extensionEntriesByName.set(pluginName, {
|
||||
id: pluginName,
|
||||
name: pluginName,
|
||||
description: pluginDescription,
|
||||
path: relPath,
|
||||
tags: extensionKeywords,
|
||||
itemCount: 1,
|
||||
items: [extensionItem],
|
||||
generatedFromExtension: true,
|
||||
lastUpdated: getDirectoryLastUpdated(gitDates, relPath),
|
||||
searchText: `${pluginName} ${pluginDescription} ${extensionKeywords.join(" ")} canvas extension`.toLowerCase(),
|
||||
});
|
||||
} catch (e) {
|
||||
console.warn(`Failed to parse extension plugin manifest for ${extensionDirName}: ${e.message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const dir of pluginDirs) {
|
||||
const pluginDir = path.join(PLUGINS_DIR, dir.name);
|
||||
const jsonPath = path.join(pluginDir, ".github/plugin", "plugin.json");
|
||||
@@ -578,7 +626,18 @@ function generatePluginsData(gitDates) {
|
||||
try {
|
||||
const data = JSON.parse(fs.readFileSync(jsonPath, "utf-8"));
|
||||
const relPath = `plugins/${dir.name}`;
|
||||
const dates = gitDates[relPath] || gitDates[`${relPath}/`] || {};
|
||||
const extensionRefs = Array.isArray(data?.["x-awesome-copilot"]?.extensions)
|
||||
? data["x-awesome-copilot"].extensions
|
||||
: [];
|
||||
const extensionItems = extensionRefs
|
||||
.map((entry) => normalizeText(entry))
|
||||
.filter(Boolean)
|
||||
.map((entry) => entry.replace(/^\.\/+/, "").replace(/\/$/, ""))
|
||||
.filter((entry) => entry.startsWith("extensions/"))
|
||||
.map((entry) => ({
|
||||
kind: "extension",
|
||||
path: entry,
|
||||
}));
|
||||
|
||||
const agentItems = (data.agents || []).flatMap((agent) => {
|
||||
const agentPath = agent.replace("./", "");
|
||||
@@ -601,27 +660,34 @@ function generatePluginsData(gitDates) {
|
||||
...agentItems,
|
||||
...(data.commands || []).map((p) => ({ kind: "prompt", path: p })),
|
||||
...(data.skills || []).map((p) => ({ kind: "skill", path: p })),
|
||||
...extensionItems,
|
||||
];
|
||||
|
||||
const tags = data.keywords || data.tags || [];
|
||||
const pluginName = data.name || dir.name;
|
||||
|
||||
plugins.push({
|
||||
id: dir.name,
|
||||
name: data.name || dir.name,
|
||||
name: pluginName,
|
||||
description: data.description || "",
|
||||
path: relPath,
|
||||
tags: tags,
|
||||
itemCount: items.length,
|
||||
items: items,
|
||||
lastUpdated: dates.lastModified || null,
|
||||
searchText: `${data.name || dir.name} ${data.description || ""
|
||||
lastUpdated: getDirectoryLastUpdated(gitDates, relPath),
|
||||
searchText: `${pluginName} ${data.description || ""
|
||||
} ${tags.join(" ")}`.toLowerCase(),
|
||||
});
|
||||
extensionEntriesByName.delete(pluginName);
|
||||
} catch (e) {
|
||||
console.warn(`Failed to parse plugin: ${dir.name}`, e.message);
|
||||
}
|
||||
}
|
||||
|
||||
for (const extensionPlugin of extensionEntriesByName.values()) {
|
||||
plugins.push(extensionPlugin);
|
||||
}
|
||||
|
||||
// Load external plugins from plugins/external.json
|
||||
const externalJsonPath = path.join(PLUGINS_DIR, "external.json");
|
||||
if (fs.existsSync(externalJsonPath)) {
|
||||
@@ -994,6 +1060,94 @@ function normalizeExternalScreenshotRole(value, ref) {
|
||||
};
|
||||
}
|
||||
|
||||
function normalizeExtensionScreenshotRole(value, relPath, ref) {
|
||||
if (!value) return null;
|
||||
if (typeof value === "string") {
|
||||
if (/^https?:\/\//i.test(value)) {
|
||||
return {
|
||||
path: null,
|
||||
type: getImageMimeType(value),
|
||||
imageUrl: value,
|
||||
};
|
||||
}
|
||||
|
||||
const normalized = value.replace(/\\/g, "/").replace(/^\.\/+/, "");
|
||||
const repoPath = normalized.startsWith(`${relPath}/`) ? normalized : `${relPath}/${normalized}`;
|
||||
return {
|
||||
path: repoPath,
|
||||
type: getImageMimeType(repoPath),
|
||||
imageUrl: buildRepoImageUrl(repoPath, ref),
|
||||
};
|
||||
}
|
||||
|
||||
const pathValue = normalizeText(value.path);
|
||||
const urlValue = normalizeText(value.url);
|
||||
if (!pathValue && !urlValue) return null;
|
||||
const pathEntry = pathValue
|
||||
? normalizeExtensionScreenshotRole(pathValue, relPath, ref)
|
||||
: null;
|
||||
const urlEntry = urlValue
|
||||
? normalizeExtensionScreenshotRole(urlValue, relPath, ref)
|
||||
: null;
|
||||
|
||||
return {
|
||||
path: pathEntry?.path || null,
|
||||
type: normalizeText(value.type) || pathEntry?.type || urlEntry?.type || null,
|
||||
imageUrl: urlEntry?.imageUrl || pathEntry?.imageUrl || null,
|
||||
};
|
||||
}
|
||||
|
||||
function resolveExtensionScreenshots(pluginJson, extensionDir, relPath, ref) {
|
||||
const inferredAssets = getExtensionAssetInfo(extensionDir, relPath, ref);
|
||||
const inferredIcon = inferredAssets?.screenshots?.icon
|
||||
? {
|
||||
path: inferredAssets.screenshots.icon.path,
|
||||
type: inferredAssets.screenshots.icon.type,
|
||||
imageUrl: inferredAssets.screenshots.icon.path
|
||||
? buildRepoImageUrl(inferredAssets.screenshots.icon.path, ref)
|
||||
: null,
|
||||
}
|
||||
: null;
|
||||
const inferredGallery = inferredAssets?.screenshots?.gallery
|
||||
? {
|
||||
path: inferredAssets.screenshots.gallery.path,
|
||||
type: inferredAssets.screenshots.gallery.type,
|
||||
imageUrl: inferredAssets.screenshots.gallery.path
|
||||
? buildRepoImageUrl(inferredAssets.screenshots.gallery.path, ref)
|
||||
: null,
|
||||
}
|
||||
: null;
|
||||
|
||||
const logoEntry = normalizeExtensionScreenshotRole(pluginJson?.logo, relPath, ref);
|
||||
const screenshotConfig = pluginJson?.["x-awesome-copilot"]?.screenshots || {};
|
||||
const iconEntry = normalizeExtensionScreenshotRole(screenshotConfig.icon, relPath, ref);
|
||||
const galleryRaw = screenshotConfig.gallery;
|
||||
const firstGalleryEntry = Array.isArray(galleryRaw) ? galleryRaw[0] : galleryRaw;
|
||||
const galleryEntry = normalizeExtensionScreenshotRole(firstGalleryEntry, relPath, ref);
|
||||
|
||||
const finalIcon = iconEntry || logoEntry || inferredIcon;
|
||||
const finalGallery = galleryEntry || logoEntry || inferredGallery || finalIcon;
|
||||
|
||||
return {
|
||||
screenshots: {
|
||||
icon: finalIcon
|
||||
? {
|
||||
path: finalIcon.path,
|
||||
type: finalIcon.type,
|
||||
}
|
||||
: null,
|
||||
gallery: finalGallery
|
||||
? {
|
||||
path: finalGallery.path,
|
||||
type: finalGallery.type,
|
||||
}
|
||||
: null,
|
||||
},
|
||||
assetPath: finalIcon?.path || inferredAssets?.assetPath || null,
|
||||
imageUrl: finalIcon?.imageUrl || inferredAssets?.imageUrl || null,
|
||||
};
|
||||
}
|
||||
|
||||
function generateCanvasManifest(gitDates, commitSha) {
|
||||
const items = [];
|
||||
|
||||
@@ -1021,17 +1175,28 @@ function generateCanvasManifest(gitDates, commitSha) {
|
||||
const packageJson = fs.existsSync(packageJsonPath)
|
||||
? JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"))
|
||||
: {};
|
||||
const canvasJsonPath = path.join(extensionDir, "canvas.json");
|
||||
const canvasJson = fs.existsSync(canvasJsonPath)
|
||||
? JSON.parse(fs.readFileSync(canvasJsonPath, "utf-8"))
|
||||
const pluginJsonPath = path.join(extensionDir, ".github", "plugin", "plugin.json");
|
||||
const pluginJson = fs.existsSync(pluginJsonPath)
|
||||
? JSON.parse(fs.readFileSync(pluginJsonPath, "utf-8"))
|
||||
: {};
|
||||
const keywords = Array.isArray(packageJson.keywords)
|
||||
? [...new Set(packageJson.keywords.filter((keyword) => typeof keyword === "string").map((keyword) => keyword.trim()).filter(Boolean))].sort((a, b) => a.localeCompare(b))
|
||||
: [];
|
||||
const extensionDescription = normalizeText(packageJson.description, "Canvas extension");
|
||||
const extensionName = normalizeText(packageJson.name, dir.name);
|
||||
const extensionVersion = normalizeText(packageJson.version, "1.0.0");
|
||||
const screenshots = getExtensionAssetInfo(extensionDir, relPath, commitSha);
|
||||
const keywordsSource = Array.isArray(pluginJson.keywords)
|
||||
? pluginJson.keywords
|
||||
: Array.isArray(packageJson.keywords)
|
||||
? packageJson.keywords
|
||||
: [];
|
||||
const keywords = [...new Set(
|
||||
keywordsSource
|
||||
.filter((keyword) => typeof keyword === "string")
|
||||
.map((keyword) => keyword.trim())
|
||||
.filter(Boolean)
|
||||
)].sort((a, b) => a.localeCompare(b));
|
||||
const extensionDescription = normalizeText(
|
||||
pluginJson.description,
|
||||
normalizeText(packageJson.description, "Canvas extension")
|
||||
);
|
||||
const extensionName = normalizeText(pluginJson.name, normalizeText(packageJson.name, dir.name));
|
||||
const extensionVersion = normalizeText(pluginJson.version, normalizeText(packageJson.version, "1.0.0"));
|
||||
const screenshots = resolveExtensionScreenshots(pluginJson, extensionDir, relPath, commitSha);
|
||||
const canvasFiles = getExtensionCanvasFiles(extensionDir);
|
||||
const canvases = [];
|
||||
for (const canvasFile of canvasFiles) {
|
||||
@@ -1045,6 +1210,7 @@ function generateCanvasManifest(gitDates, commitSha) {
|
||||
/\\/g,
|
||||
"/"
|
||||
)}`;
|
||||
const installCommand = `copilot plugin install ${extensionName}@awesome-copilot`;
|
||||
|
||||
for (const canvas of canvasEntries) {
|
||||
const canvasId = normalizeText(canvas.id, dir.name);
|
||||
@@ -1055,6 +1221,7 @@ function generateCanvasManifest(gitDates, commitSha) {
|
||||
canvasId,
|
||||
extensionId: dir.name,
|
||||
extensionName,
|
||||
pluginName: extensionName,
|
||||
name: canvasName,
|
||||
version: extensionVersion,
|
||||
description: canvasDescription,
|
||||
@@ -1065,9 +1232,10 @@ function generateCanvasManifest(gitDates, commitSha) {
|
||||
imageUrl: screenshots?.imageUrl || null,
|
||||
assetPath: screenshots?.assetPath || null,
|
||||
installUrl,
|
||||
installCommand,
|
||||
sourceUrl: null,
|
||||
external: false,
|
||||
author: normalizeAuthor(canvasJson.author),
|
||||
author: normalizeAuthor(pluginJson.author),
|
||||
keywords,
|
||||
});
|
||||
}
|
||||
@@ -1128,6 +1296,7 @@ function generateCanvasManifest(gitDates, commitSha) {
|
||||
canvasId,
|
||||
extensionId: id,
|
||||
extensionName: name,
|
||||
pluginName: null,
|
||||
name,
|
||||
version: normalizeText(ext?.version, "1.0.0"),
|
||||
description: normalizeText(ext?.description, "External canvas extension"),
|
||||
@@ -1138,6 +1307,7 @@ function generateCanvasManifest(gitDates, commitSha) {
|
||||
imageUrl,
|
||||
assetPath,
|
||||
installUrl,
|
||||
installCommand: null,
|
||||
sourceUrl: sourceUrl || null,
|
||||
external: true,
|
||||
author: normalizeAuthor(ext?.author),
|
||||
@@ -1163,12 +1333,12 @@ function generateCanvasManifest(gitDates, commitSha) {
|
||||
};
|
||||
}
|
||||
|
||||
function generateExtensionsData(canvasManifestData) {
|
||||
if (!canvasManifestData || !Array.isArray(canvasManifestData.items)) {
|
||||
function generateExtensionsData(extensionManifestData) {
|
||||
if (!extensionManifestData || !Array.isArray(extensionManifestData.items)) {
|
||||
return { items: [], filters: { keywords: [] } };
|
||||
}
|
||||
|
||||
const items = canvasManifestData.items.map((item) => ({
|
||||
const items = extensionManifestData.items.map((item) => ({
|
||||
...item,
|
||||
keywords: Array.isArray(item.keywords) ? item.keywords : [],
|
||||
screenshots: item.screenshots || { icon: null, gallery: null },
|
||||
@@ -1182,69 +1352,6 @@ function generateExtensionsData(canvasManifestData) {
|
||||
return { items, filters };
|
||||
}
|
||||
|
||||
function writePerExtensionCanvasManifests(canvasManifestData) {
|
||||
const manifests = new Map();
|
||||
|
||||
function toExtensionRelativePath(assetPath, extensionId) {
|
||||
const normalizedPath = normalizeText(assetPath).replace(/\\/g, "/");
|
||||
if (!normalizedPath) return null;
|
||||
const prefix = `extensions/${extensionId}/`;
|
||||
return normalizedPath.startsWith(prefix)
|
||||
? normalizedPath.slice(prefix.length)
|
||||
: normalizedPath;
|
||||
}
|
||||
|
||||
function toRelativeScreenshots(screenshots, extensionId) {
|
||||
if (!screenshots) return { icon: null, gallery: null };
|
||||
const toRelativeEntry = (entry) =>
|
||||
entry
|
||||
? {
|
||||
...entry,
|
||||
path: toExtensionRelativePath(entry.path, extensionId),
|
||||
}
|
||||
: null;
|
||||
return {
|
||||
icon: toRelativeEntry(screenshots.icon),
|
||||
gallery: toRelativeEntry(screenshots.gallery),
|
||||
};
|
||||
}
|
||||
|
||||
for (const item of canvasManifestData.items || []) {
|
||||
if (!item || item.external || !item.extensionId || !item.path) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// We assume one canvas per extension folder.
|
||||
if (manifests.has(item.extensionId)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
manifests.set(item.extensionId, {
|
||||
id: item.canvasId || item.id,
|
||||
name: item.name,
|
||||
description: item.description || "Canvas extension",
|
||||
version: item.version || "1.0.0",
|
||||
...(item.author ? { author: item.author } : {}),
|
||||
keywords: Array.isArray(item.keywords)
|
||||
? [...new Set(item.keywords)].sort((a, b) => a.localeCompare(b))
|
||||
: [],
|
||||
screenshots: toRelativeScreenshots(
|
||||
item.screenshots || { icon: null, gallery: null },
|
||||
item.extensionId
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
for (const [extensionId, manifest] of manifests.entries()) {
|
||||
const canvasManifestPath = path.join(
|
||||
EXTENSIONS_DIR,
|
||||
extensionId,
|
||||
"canvas.json"
|
||||
);
|
||||
fs.writeFileSync(canvasManifestPath, JSON.stringify(manifest, null, 2));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate tools metadata from website/data/tools.yml
|
||||
*/
|
||||
@@ -1585,8 +1692,8 @@ async function main() {
|
||||
`✓ Generated ${plugins.length} plugins (${pluginsData.filters.tags.length} tags)`
|
||||
);
|
||||
|
||||
const canvasManifestData = generateCanvasManifest(gitDates, commitSha);
|
||||
const extensionsData = generateExtensionsData(canvasManifestData);
|
||||
const extensionManifestData = generateCanvasManifest(gitDates, commitSha);
|
||||
const extensionsData = generateExtensionsData(extensionManifestData);
|
||||
const extensions = extensionsData.items;
|
||||
console.log(
|
||||
`✓ Generated ${extensions.length} extensions (${extensionsData.filters.keywords.length} keywords)`
|
||||
@@ -1655,8 +1762,6 @@ async function main() {
|
||||
JSON.stringify(extensionsData, null, 2)
|
||||
);
|
||||
|
||||
writePerExtensionCanvasManifests(canvasManifestData);
|
||||
|
||||
fs.writeFileSync(
|
||||
path.join(WEBSITE_DATA_DIR, "tools.json"),
|
||||
JSON.stringify(toolsData, null, 2)
|
||||
|
||||
Reference in New Issue
Block a user