chore: publish from main

This commit is contained in:
github-actions[bot]
2026-07-03 02:25:08 +00:00
parent c919c0c570
commit 700e6f19dd
27 changed files with 748 additions and 687 deletions
+190 -85
View File
@@ -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)