Files
awesome-copilot/eng/materialize-plugins.mjs
Aaron Powell 9d1df57ebc refactor: migrate plugins to Claude Code spec format
- 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>
2026-02-18 14:22:50 +11:00

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();