mirror of
https://github.com/github/awesome-copilot.git
synced 2026-07-14 10:01:06 +00:00
e986f49695
* 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>
199 lines
6.4 KiB
JavaScript
199 lines
6.4 KiB
JavaScript
#!/usr/bin/env node
|
|
|
|
import fs from "fs";
|
|
import path from "path";
|
|
import { ROOT_FOLDER } from "./constants.mjs";
|
|
|
|
const PLUGINS_DIR = path.join(ROOT_FOLDER, "plugins");
|
|
|
|
/**
|
|
* Recursively copy a directory.
|
|
*/
|
|
function copyDirRecursive(src, dest) {
|
|
fs.mkdirSync(dest, { recursive: true });
|
|
for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
|
|
const srcPath = path.join(src, entry.name);
|
|
const destPath = path.join(dest, entry.name);
|
|
if (entry.isDirectory()) {
|
|
copyDirRecursive(srcPath, destPath);
|
|
} else {
|
|
fs.copyFileSync(srcPath, destPath);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Resolve a plugin-relative path to the repo-root source file.
|
|
*
|
|
* ./agents/foo.md → ROOT/agents/foo.agent.md
|
|
* ./skills/baz/ → ROOT/skills/baz/
|
|
*/
|
|
function resolveSource(relPath) {
|
|
const basename = path.basename(relPath, ".md");
|
|
if (relPath.startsWith("./agents/")) {
|
|
return path.join(ROOT_FOLDER, "agents", `${basename}.agent.md`);
|
|
}
|
|
if (relPath.startsWith("./skills/")) {
|
|
// Strip trailing slash and get the skill folder name
|
|
const skillName = relPath.replace(/^\.\/skills\//, "").replace(/\/$/, "");
|
|
return path.join(ROOT_FOLDER, "skills", skillName);
|
|
}
|
|
if (relPath.startsWith("./extensions/")) {
|
|
const extensionName = relPath.replace(/^\.\/extensions\//, "").replace(/\/$/, "");
|
|
return path.join(ROOT_FOLDER, "extensions", extensionName);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
function materializePlugins() {
|
|
console.log("Materializing plugin files...\n");
|
|
|
|
if (!fs.existsSync(PLUGINS_DIR)) {
|
|
console.error(`Error: Plugins directory not found at ${PLUGINS_DIR}`);
|
|
process.exit(1);
|
|
}
|
|
|
|
const pluginDirs = fs.readdirSync(PLUGINS_DIR, { withFileTypes: true })
|
|
.filter(entry => entry.isDirectory())
|
|
.map(entry => entry.name)
|
|
.sort();
|
|
|
|
let totalAgents = 0;
|
|
let totalSkills = 0;
|
|
let totalExtensions = 0;
|
|
let warnings = 0;
|
|
let errors = 0;
|
|
|
|
for (const dirName of pluginDirs) {
|
|
const pluginPath = path.join(PLUGINS_DIR, dirName);
|
|
const pluginJsonPath = path.join(pluginPath, ".github/plugin", "plugin.json");
|
|
|
|
if (!fs.existsSync(pluginJsonPath)) {
|
|
continue;
|
|
}
|
|
|
|
let metadata;
|
|
try {
|
|
metadata = JSON.parse(fs.readFileSync(pluginJsonPath, "utf8"));
|
|
} catch (err) {
|
|
console.error(`Error: Failed to parse ${pluginJsonPath}: ${err.message}`);
|
|
errors++;
|
|
continue;
|
|
}
|
|
|
|
const pluginName = metadata.name || dirName;
|
|
|
|
// Process agents
|
|
if (Array.isArray(metadata.agents)) {
|
|
for (const relPath of metadata.agents) {
|
|
const src = resolveSource(relPath);
|
|
if (!src) {
|
|
console.warn(` ⚠ ${pluginName}: Unknown path format: ${relPath}`);
|
|
warnings++;
|
|
continue;
|
|
}
|
|
if (!fs.existsSync(src)) {
|
|
console.warn(` ⚠ ${pluginName}: Source not found: ${src}`);
|
|
warnings++;
|
|
continue;
|
|
}
|
|
const dest = path.join(pluginPath, relPath.replace(/^\.\//, ""));
|
|
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
fs.copyFileSync(src, dest);
|
|
totalAgents++;
|
|
}
|
|
}
|
|
|
|
// Process skills
|
|
if (Array.isArray(metadata.skills)) {
|
|
for (const relPath of metadata.skills) {
|
|
const src = resolveSource(relPath);
|
|
if (!src) {
|
|
console.warn(` ⚠ ${pluginName}: Unknown path format: ${relPath}`);
|
|
warnings++;
|
|
continue;
|
|
}
|
|
if (!fs.existsSync(src) || !fs.statSync(src).isDirectory()) {
|
|
console.warn(` ⚠ ${pluginName}: Source directory not found: ${src}`);
|
|
warnings++;
|
|
continue;
|
|
}
|
|
const dest = path.join(pluginPath, relPath.replace(/^\.\//, "").replace(/\/$/, ""));
|
|
copyDirRecursive(src, dest);
|
|
totalSkills++;
|
|
}
|
|
}
|
|
|
|
// Process extension references from x-awesome-copilot.extensions
|
|
const extensionRefs = Array.isArray(metadata?.["x-awesome-copilot"]?.extensions)
|
|
? metadata["x-awesome-copilot"].extensions
|
|
: [];
|
|
for (const relPath of extensionRefs) {
|
|
const src = resolveSource(relPath);
|
|
if (!src) {
|
|
console.warn(` ⚠ ${pluginName}: Unknown extension path format: ${relPath}`);
|
|
warnings++;
|
|
continue;
|
|
}
|
|
if (!fs.existsSync(src) || !fs.statSync(src).isDirectory()) {
|
|
console.warn(` ⚠ ${pluginName}: Extension source directory not found: ${src}`);
|
|
warnings++;
|
|
continue;
|
|
}
|
|
const dest = path.join(pluginPath, relPath.replace(/^\.\//, "").replace(/\/$/, ""));
|
|
copyDirRecursive(src, dest);
|
|
totalExtensions++;
|
|
}
|
|
|
|
// Rewrite plugin.json to use folder paths instead of individual file paths.
|
|
// On staged, paths like ./agents/foo.md point to individual source files.
|
|
// On main, after materialization, we only need the containing directory.
|
|
const rewritten = { ...metadata };
|
|
let changed = false;
|
|
|
|
for (const field of ["agents", "commands"]) {
|
|
if (Array.isArray(rewritten[field]) && rewritten[field].length > 0) {
|
|
const dirs = [...new Set(rewritten[field].map(p => path.dirname(p)))];
|
|
rewritten[field] = dirs;
|
|
changed = true;
|
|
}
|
|
}
|
|
|
|
if (Array.isArray(rewritten.skills) && rewritten.skills.length > 0) {
|
|
// Skills are already folder refs (./skills/name/); strip trailing slash
|
|
rewritten.skills = rewritten.skills.map(p => p.replace(/\/$/, ""));
|
|
changed = true;
|
|
}
|
|
|
|
if (Array.isArray(rewritten?.["x-awesome-copilot"]?.extensions) &&
|
|
rewritten["x-awesome-copilot"].extensions.length > 0) {
|
|
rewritten["x-awesome-copilot"].extensions =
|
|
rewritten["x-awesome-copilot"].extensions.map((p) => p.replace(/\/$/, ""));
|
|
changed = true;
|
|
}
|
|
|
|
if (changed) {
|
|
fs.writeFileSync(pluginJsonPath, JSON.stringify(rewritten, null, 2) + "\n", "utf8");
|
|
}
|
|
|
|
const counts = [];
|
|
if (metadata.agents?.length) counts.push(`${metadata.agents.length} agents`);
|
|
if (metadata.skills?.length) counts.push(`${metadata.skills.length} skills`);
|
|
if (extensionRefs.length) counts.push(`${extensionRefs.length} extensions`);
|
|
if (counts.length) {
|
|
console.log(`✓ ${pluginName}: ${counts.join(", ")}`);
|
|
}
|
|
}
|
|
|
|
console.log(`\nDone. Copied ${totalAgents} agents, ${totalSkills} skills, ${totalExtensions} extensions.`);
|
|
if (warnings > 0) {
|
|
console.log(`${warnings} warning(s).`);
|
|
}
|
|
if (errors > 0) {
|
|
console.error(`${errors} error(s).`);
|
|
process.exit(1);
|
|
}
|
|
}
|
|
|
|
materializePlugins();
|