mirror of
https://github.com/github/awesome-copilot.git
synced 2026-06-13 19:34:54 +00:00
fix: skill-validator invocation for .github/plugin/plugin.json convention (#1916)
* fix: skill-validator invocation for .github/plugin/plugin.json convention The skill-validator --plugin mode looks for plugin.json at <dir>/plugin.json, but external plugins (and the Copilot CLI) place it at .github/plugin/plugin.json. This caused every external plugin with skills or agents to fail the skill-validator gate with a misleading 'No plugin.json found' error, even when the install smoke test passed correctly. Extract buildSkillValidatorArgs() which reads plugin.json from .github/plugin/plugin.json, resolves skills/agents paths relative to the plugin root, and invokes skill-validator with --skills/--agents instead of --plugin. Falls back to --plugin if the conventional path is not present. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: also check .plugins/plugin.json and root plugin.json locations Extend buildSkillValidatorArgs to probe three candidate plugin.json locations in priority order before falling back to --plugin: 1. .github/plugin/plugin.json (Copilot CLI convention) 2. .plugins/plugin.json 3. plugin.json (root — also the skill-validator's native --plugin expectation) Extract findPluginJson() and PLUGIN_JSON_CANDIDATES constant so the list is easy to extend. Paths in plugin.json are always resolved relative to the plugin root regardless of where the manifest lives. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -156,10 +156,87 @@ function downloadSkillValidator(workDir) {
|
||||
return binaryPath;
|
||||
}
|
||||
|
||||
// Ordered list of candidate locations for plugin.json, from most to least specific.
|
||||
// The skill-validator --plugin mode expects plugin.json at the plugin root, but
|
||||
// both the Copilot CLI and many external repos use nested conventions. We read the
|
||||
// manifest ourselves so skill/agent paths can be resolved from the plugin root
|
||||
// consistently, regardless of where the manifest lives.
|
||||
const PLUGIN_JSON_CANDIDATES = [
|
||||
[".github", "plugin", "plugin.json"],
|
||||
[".plugins", "plugin.json"],
|
||||
["plugin.json"],
|
||||
];
|
||||
|
||||
function findPluginJson(pluginRoot) {
|
||||
for (const segments of PLUGIN_JSON_CANDIDATES) {
|
||||
const candidate = path.join(pluginRoot, ...segments);
|
||||
if (fs.existsSync(candidate)) {
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function buildSkillValidatorArgs(pluginRoot) {
|
||||
const pluginJsonPath = findPluginJson(pluginRoot);
|
||||
if (!pluginJsonPath) {
|
||||
// No recognised plugin.json location found — let the validator fail with its
|
||||
// own diagnostic (covers exotic layouts and surfaces the real error to submitters).
|
||||
return ["check", "--verbose", "--plugin", pluginRoot];
|
||||
}
|
||||
|
||||
let pluginJson;
|
||||
try {
|
||||
pluginJson = JSON.parse(fs.readFileSync(pluginJsonPath, "utf8"));
|
||||
} catch {
|
||||
// Malformed plugin.json — let the validator surface the parse error.
|
||||
return ["check", "--verbose", "--plugin", pluginRoot];
|
||||
}
|
||||
|
||||
const args = ["check", "--verbose"];
|
||||
|
||||
// Paths in plugin.json are relative to the plugin root regardless of where
|
||||
// plugin.json itself lives. Use [].concat() to accept both string and array values.
|
||||
const skillPaths = [].concat(pluginJson.skills ?? [])
|
||||
.map((s) => path.resolve(pluginRoot, s))
|
||||
.filter((p) => fs.existsSync(p));
|
||||
|
||||
// Agent entries may be directory paths or explicit file paths; normalise to directories
|
||||
// so AgentDiscovery.DiscoverAgentsInDirectory can discover agents within them.
|
||||
// Deduplicate in case multiple file entries share the same parent directory.
|
||||
const agentPaths = [...new Set(
|
||||
[].concat(pluginJson.agents ?? [])
|
||||
.map((a) => {
|
||||
const resolved = path.resolve(pluginRoot, a);
|
||||
if (fs.existsSync(resolved) && fs.statSync(resolved).isFile()) {
|
||||
return path.dirname(resolved);
|
||||
}
|
||||
return resolved;
|
||||
})
|
||||
.filter((p) => fs.existsSync(p))
|
||||
)];
|
||||
|
||||
if (skillPaths.length > 0) {
|
||||
args.push("--skills", ...skillPaths);
|
||||
}
|
||||
if (agentPaths.length > 0) {
|
||||
args.push("--agents", ...agentPaths);
|
||||
}
|
||||
|
||||
if (skillPaths.length === 0 && agentPaths.length === 0) {
|
||||
// plugin.json found but no resolvable skills/agents — fall back to --plugin so the
|
||||
// validator can surface the specific validation error to the submitter.
|
||||
return ["check", "--verbose", "--plugin", pluginRoot];
|
||||
}
|
||||
|
||||
return args;
|
||||
}
|
||||
|
||||
function runSkillValidatorGate(workDir, pluginRoot) {
|
||||
try {
|
||||
const validatorBinary = downloadSkillValidator(workDir);
|
||||
const check = runCommand(validatorBinary, ["check", "--verbose", "--plugin", pluginRoot]);
|
||||
const args = buildSkillValidatorArgs(pluginRoot);
|
||||
const check = runCommand(validatorBinary, args);
|
||||
|
||||
if (check.exitCode === 0) {
|
||||
return { status: "pass", output: check.output };
|
||||
|
||||
Reference in New Issue
Block a user