mirror of
https://github.com/github/awesome-copilot.git
synced 2026-06-17 13:11:27 +00:00
Automate external plugin update PR quality checks (#2005)
* Add PR quality gates for external plugin updates Automate external plugin update PR review by running skill-validator and install smoke checks against changed entries in plugins/external.json. Sync PR workflow-state labels and upsert a marker-based status comment with source tree links for each changed plugin. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Apply suggestions from code review Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * Tighten external plugin PR workflow permissions Scope write permissions to the PR synchronization job, keep the quality-gate job read-only, and handle no-op and detection-failure states explicitly. Also fix source tree link encoding for refs, SHAs, and plugin paths. Co-authored-by: Copilot <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> * Fix external plugin workflow job steps Co-authored-by: aaronpowell <434140+aaronpowell@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: aaronpowell <434140+aaronpowell@users.noreply.github.com>
This commit is contained in:
@@ -0,0 +1,125 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import { runExternalPluginQualityGates } from "./external-plugin-quality-gates.mjs";
|
||||
|
||||
function normalizePluginPath(pluginPath) {
|
||||
if (!pluginPath || pluginPath === "/") {
|
||||
return "";
|
||||
}
|
||||
|
||||
return String(pluginPath).trim().replace(/^\/+|\/+$/g, "");
|
||||
}
|
||||
|
||||
function encodePathLikeValue(value) {
|
||||
return String(value)
|
||||
.split("/")
|
||||
.map((segment) => encodeURIComponent(segment))
|
||||
.join("/");
|
||||
}
|
||||
|
||||
export function buildSourceTreeUrl(plugin) {
|
||||
const sourceRepo = plugin?.source?.repo;
|
||||
if (!sourceRepo) {
|
||||
return "";
|
||||
}
|
||||
|
||||
const sourceLocator = plugin?.source?.sha || plugin?.source?.ref;
|
||||
if (!sourceLocator) {
|
||||
return `https://github.com/${sourceRepo}`;
|
||||
}
|
||||
|
||||
const encodedLocator = encodeURIComponent(sourceLocator);
|
||||
const normalizedPath = normalizePluginPath(plugin?.source?.path);
|
||||
if (!normalizedPath) {
|
||||
return `https://github.com/${sourceRepo}/tree/${encodedLocator}`;
|
||||
}
|
||||
|
||||
const encodedPath = encodePathLikeValue(normalizedPath);
|
||||
return `https://github.com/${sourceRepo}/tree/${encodedLocator}/${encodedPath}`;
|
||||
}
|
||||
|
||||
function aggregateResultStatus(pluginResults) {
|
||||
if (pluginResults.some((entry) => entry.quality?.overall_status === "fail")) {
|
||||
return {
|
||||
overallStatus: "fail",
|
||||
failureClass: "submitter_fixes",
|
||||
};
|
||||
}
|
||||
|
||||
if (pluginResults.some((entry) => entry.quality?.overall_status === "infra_error")) {
|
||||
return {
|
||||
overallStatus: "infra_error",
|
||||
failureClass: "infra",
|
||||
};
|
||||
}
|
||||
|
||||
if (pluginResults.length === 0) {
|
||||
return {
|
||||
overallStatus: "not_run",
|
||||
failureClass: "none",
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
overallStatus: "pass",
|
||||
failureClass: "none",
|
||||
};
|
||||
}
|
||||
|
||||
export function runExternalPluginPrQualityGates(plugins) {
|
||||
if (!Array.isArray(plugins)) {
|
||||
throw new Error("plugins must be an array");
|
||||
}
|
||||
|
||||
const checkedPlugins = plugins.map((plugin) => {
|
||||
const quality = runExternalPluginQualityGates(plugin);
|
||||
return {
|
||||
name: plugin?.name ?? "unknown",
|
||||
source: plugin?.source ?? {},
|
||||
source_tree_url: buildSourceTreeUrl(plugin),
|
||||
quality,
|
||||
};
|
||||
});
|
||||
|
||||
const aggregate = aggregateResultStatus(checkedPlugins);
|
||||
const summary = checkedPlugins.length === 0
|
||||
? "No changed external plugin entries were detected in plugins/external.json."
|
||||
: checkedPlugins
|
||||
.map((entry) =>
|
||||
`- ${entry.name}: skill-validator=${entry.quality.skill_validator_status}, install-smoke=${entry.quality.smoke_status}, overall=${entry.quality.overall_status}`
|
||||
)
|
||||
.join("\n");
|
||||
|
||||
return {
|
||||
overall_status: aggregate.overallStatus,
|
||||
failure_class: aggregate.failureClass,
|
||||
summary,
|
||||
checked_plugins: checkedPlugins,
|
||||
};
|
||||
}
|
||||
|
||||
function parseCliArgs(argv) {
|
||||
const args = {};
|
||||
for (let index = 0; index < argv.length; index += 1) {
|
||||
const key = argv[index];
|
||||
if (!key.startsWith("--")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
args[key.slice(2)] = argv[index + 1];
|
||||
index += 1;
|
||||
}
|
||||
return args;
|
||||
}
|
||||
|
||||
if (import.meta.url === `file://${process.argv[1]}`) {
|
||||
const args = parseCliArgs(process.argv.slice(2));
|
||||
if (!args["plugins-json"]) {
|
||||
console.error("Usage: node ./eng/external-plugin-pr-quality-gates.mjs --plugins-json '<json-array>'");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const plugins = JSON.parse(args["plugins-json"]);
|
||||
const result = runExternalPluginPrQualityGates(plugins);
|
||||
process.stdout.write(`${JSON.stringify(result)}\n`);
|
||||
}
|
||||
Reference in New Issue
Block a user