mirror of
https://github.com/github/awesome-copilot.git
synced 2026-02-20 02:15:12 +00:00
- Move plugin manifests from .github/plugin/ to .claude-plugin/ - Convert items[] to Claude Code spec fields (agents, commands, skills) - Rename tags to keywords, drop display/featured/instructions from plugins - Delete all symlinks and materialized files from plugin directories - Add eng/materialize-plugins.mjs to copy source files into plugin dirs at publish time - Add .github/workflows/publish.yml for staged->main publishing - Update CI triggers to target staged branch - Update validation, creation, marketplace, and README generation scripts - Update CONTRIBUTING.md and AGENTS.md documentation - Include all new content from main (polyglot-test-agent, gem-browser-tester, fabric-lakehouse, fluentui-blazor, quasi-coder, transloadit-media-processing, make-repo-contribution hardening, website logo/gradient changes) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
168 lines
5.0 KiB
JavaScript
168 lines
5.0 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
|
|
* ./commands/bar.md → ROOT/prompts/bar.prompt.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("./commands/")) {
|
|
return path.join(ROOT_FOLDER, "prompts", `${basename}.prompt.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);
|
|
}
|
|
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 totalCommands = 0;
|
|
let totalSkills = 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 commands
|
|
if (Array.isArray(metadata.commands)) {
|
|
for (const relPath of metadata.commands) {
|
|
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);
|
|
totalCommands++;
|
|
}
|
|
}
|
|
|
|
// 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++;
|
|
}
|
|
}
|
|
|
|
const counts = [];
|
|
if (metadata.agents?.length) counts.push(`${metadata.agents.length} agents`);
|
|
if (metadata.commands?.length) counts.push(`${metadata.commands.length} commands`);
|
|
if (metadata.skills?.length) counts.push(`${metadata.skills.length} skills`);
|
|
if (counts.length) {
|
|
console.log(`✓ ${pluginName}: ${counts.join(", ")}`);
|
|
}
|
|
}
|
|
|
|
console.log(`\nDone. Copied ${totalAgents} agents, ${totalCommands} commands, ${totalSkills} skills.`);
|
|
if (warnings > 0) {
|
|
console.log(`${warnings} warning(s).`);
|
|
}
|
|
if (errors > 0) {
|
|
console.error(`${errors} error(s).`);
|
|
process.exit(1);
|
|
}
|
|
}
|
|
|
|
materializePlugins();
|