mirror of
https://github.com/github/awesome-copilot.git
synced 2026-06-14 20:05:15 +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;
|
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) {
|
function runSkillValidatorGate(workDir, pluginRoot) {
|
||||||
try {
|
try {
|
||||||
const validatorBinary = downloadSkillValidator(workDir);
|
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) {
|
if (check.exitCode === 0) {
|
||||||
return { status: "pass", output: check.output };
|
return { status: "pass", output: check.output };
|
||||||
|
|||||||
Reference in New Issue
Block a user