Harden external plugin PR quality gate rendering and name validation (#2444)

* Harden external plugin PR quality gates

Reference: https://github.com/github/awesome-copilot/pull/2398

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: a79923be-c65f-4d51-8fe3-a86e05fd02f1

* Potential fix for pull request finding

Co-authored-by: Copilot Autofix powered by AI <175728472+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>

---------

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Copilot-Session: a79923be-c65f-4d51-8fe3-a86e05fd02f1
This commit is contained in:
Aaron Powell
2026-07-27 15:57:06 +10:00
committed by GitHub
parent 541d8f0499
commit 8e137c3c09
3 changed files with 75 additions and 11 deletions
+22 -1
View File
@@ -1,6 +1,7 @@
#!/usr/bin/env node
import { runExternalPluginQualityGates } from "./external-plugin-quality-gates.mjs";
import { validateExternalPlugin } from "./external-plugin-validation.mjs";
function normalizePluginPath(pluginPath) {
if (!pluginPath || pluginPath === "/") {
@@ -66,13 +67,33 @@ function aggregateResultStatus(pluginResults) {
};
}
function createValidationFailureQuality(errors) {
const output = errors.map((error) => `- ${error}`).join("\n");
return {
overall_status: "fail",
vally_lint_status: "fail",
smoke_status: "not_run",
version_match_status: "not_run",
canvas_structure_status: "not_run",
failure_class: "submitter_fixes",
summary: "Plugin entry failed external.json validation. Fix the listed errors and re-run quality checks.",
vally_lint_output: output,
smoke_output: "Install smoke test skipped due to external.json validation errors.",
version_match_output: "Version match skipped due to external.json validation errors.",
canvas_structure_output: "Canvas structure check skipped due to external.json validation errors.",
};
}
export async function runExternalPluginPrQualityGates(plugins) {
if (!Array.isArray(plugins)) {
throw new Error("plugins must be an array");
}
const checkedPlugins = await Promise.all(plugins.map(async (plugin) => {
const quality = await runExternalPluginQualityGates(plugin);
const validation = validateExternalPlugin(plugin, "changed-plugin", { policy: "marketplace" });
const quality = validation.errors.length > 0
? createValidationFailureQuality(validation.errors)
: await runExternalPluginQualityGates(plugin);
return {
name: plugin?.name ?? "unknown",
source: plugin?.source ?? {},
+12 -4
View File
@@ -60,12 +60,20 @@ function validatePluginName(name, prefix, errors) {
return;
}
if (name.length > 50) {
errors.push(`${prefix}: "name" must be 50 characters or fewer`);
if (name.length > 64) {
errors.push(`${prefix}: "name" must be 64 characters or fewer`);
}
if (!/^[a-z0-9-]+$/.test(name)) {
errors.push(`${prefix}: "name" must contain only lowercase letters, numbers, and hyphens`);
if (!/^[a-z0-9.-]+$/.test(name)) {
errors.push(`${prefix}: "name" must contain only lowercase letters, numbers, hyphens, and periods`);
}
if (!/^[a-z0-9].*[a-z0-9]$/.test(name) && !/^[a-z0-9]$/.test(name)) {
errors.push(`${prefix}: "name" must start and end with a lowercase letter or number`);
}
if (name.includes("--") || name.includes("..")) {
errors.push(`${prefix}: "name" must not contain consecutive hyphens or periods`);
}
}