Enforce canonical plugin manifest ordering (#1601)

* Enforce canonical plugin manifest ordering

Sort existing plugin manifest spec arrays so plugin:clean no longer creates noisy diffs from out-of-order entries. Add validation to require alphabetical ordering and teach plugin:clean to normalize manifest arrays when cleaning materialized plugin content.

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>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
Aaron Powell
2026-05-04 12:00:50 +10:00
committed by GitHub
parent fb71887136
commit a1197525bd
26 changed files with 112 additions and 85 deletions

View File

@@ -42,6 +42,14 @@ export function restoreManifestFromMaterializedFiles(pluginPath) {
let changed = false;
for (const [field, spec] of Object.entries(MATERIALIZED_SPECS)) {
if (Array.isArray(plugin[field])) {
const sortedEntries = sortPluginEntries(plugin[field]);
if (!arraysEqual(plugin[field], sortedEntries)) {
plugin[field] = sortedEntries;
changed = true;
}
}
const materializedPath = path.join(pluginPath, spec.path);
if (!fs.existsSync(materializedPath) || !fs.statSync(materializedPath).isDirectory()) {
continue;
@@ -132,6 +140,10 @@ function arraysEqual(left, right) {
return left.every((value, index) => value === right[index]);
}
function sortPluginEntries(entries) {
return [...entries].sort((left, right) => left.localeCompare(right));
}
function toPosixPath(filePath) {
return filePath.split(path.sep).join("/");
}
@@ -165,7 +177,7 @@ function main() {
} else {
console.log(`✅ Removed ${total} materialized file(s) from plugins.`);
if (manifestsUpdated > 0) {
console.log(`✅ Updated ${manifestsUpdated} plugin manifest(s) with folder trailing slashes.`);
console.log(`✅ Updated ${manifestsUpdated} plugin manifest(s) to restore and normalize spec entries.`);
}
}
}

View File

@@ -64,6 +64,18 @@ function validateKeywords(keywords) {
return null;
}
function arraysEqual(left, right) {
if (!Array.isArray(left) || !Array.isArray(right) || left.length !== right.length) {
return false;
}
return left.every((value, index) => value === right[index]);
}
function sortPluginEntries(entries) {
return [...entries].sort((left, right) => left.localeCompare(right));
}
function validateSpecPaths(plugin) {
const errors = [];
const specs = {
@@ -78,6 +90,9 @@ function validateSpecPaths(plugin) {
errors.push(`${field} must be an array`);
continue;
}
if (!arraysEqual(arr, sortPluginEntries(arr))) {
errors.push(`${field} must be sorted alphabetically`);
}
for (let i = 0; i < arr.length; i++) {
const p = arr[i];
if (typeof p !== "string") {