mirror of
https://github.com/github/awesome-copilot.git
synced 2026-08-14 05:06:54 +00:00
fix(plugin-validation): allow parent-bundled extensions
Accept reusable canvas sources that are referenced by an existing parent plugin, matching the post-#2546 scaffolding guidance, while continuing to reject orphaned sources. Cover parent-only, standalone, and orphaned registrations. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 52be9c67-3ae4-4610-93d0-fe0b7ab95ccb
This commit is contained in:
+29
-13
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
import fs from "fs";
|
import fs from "fs";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
|
import { fileURLToPath } from "url";
|
||||||
import { ROOT_FOLDER } from "./constants.mjs";
|
import { ROOT_FOLDER } from "./constants.mjs";
|
||||||
import { readExternalPlugins } from "./external-plugin-validation.mjs";
|
import { readExternalPlugins } from "./external-plugin-validation.mjs";
|
||||||
import { validateLicenseField } from "./lib/license.mjs";
|
import { validateLicenseField } from "./lib/license.mjs";
|
||||||
@@ -334,7 +335,11 @@ function validateExtensionScreenshotPath(extensionDir, pathValue, fieldName, err
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Main validation function
|
// Main validation function
|
||||||
function validatePlugins() {
|
export function isReusableExtensionRegistered(extensionName, pluginDirectoryNames, referencedExtensionNames) {
|
||||||
|
return pluginDirectoryNames.has(extensionName) || referencedExtensionNames.has(extensionName);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function validatePlugins() {
|
||||||
const pluginDirs = fs.existsSync(PLUGINS_DIR)
|
const pluginDirs = fs.existsSync(PLUGINS_DIR)
|
||||||
? fs.readdirSync(PLUGINS_DIR, { withFileTypes: true })
|
? fs.readdirSync(PLUGINS_DIR, { withFileTypes: true })
|
||||||
.filter((d) => d.isDirectory())
|
.filter((d) => d.isDirectory())
|
||||||
@@ -350,6 +355,8 @@ function validatePlugins() {
|
|||||||
let hasErrors = false;
|
let hasErrors = false;
|
||||||
const seenNames = new Set();
|
const seenNames = new Set();
|
||||||
const localPluginNames = [];
|
const localPluginNames = [];
|
||||||
|
const pluginDirectoryNames = new Set(pluginDirs);
|
||||||
|
const referencedExtensionNames = new Set();
|
||||||
|
|
||||||
for (const dir of pluginDirs) {
|
for (const dir of pluginDirs) {
|
||||||
console.log(`Validating ${dir}...`);
|
console.log(`Validating ${dir}...`);
|
||||||
@@ -377,12 +384,20 @@ function validatePlugins() {
|
|||||||
localPluginNames.push(plugin.name);
|
localPluginNames.push(plugin.name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const extensionReferences = plugin?.extensions?.[AWESOME_COPILOT_NAMESPACE]?.extensions;
|
||||||
|
if (Array.isArray(extensionReferences)) {
|
||||||
|
for (const reference of extensionReferences) {
|
||||||
|
if (typeof reference === "string" && reference.startsWith("./extensions/")) {
|
||||||
|
referencedExtensionNames.add(reference.replace(/^\.\/extensions\//, "").replace(/\/$/, ""));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const dir of getExtensionFolderNames()) {
|
for (const dir of getExtensionFolderNames()) {
|
||||||
const pluginJsonPath = path.join(PLUGINS_DIR, dir, "plugin.json");
|
if (!isReusableExtensionRegistered(dir, pluginDirectoryNames, referencedExtensionNames)) {
|
||||||
if (!fs.existsSync(pluginJsonPath)) {
|
console.error(`❌ extension ${dir}: must be referenced by a plugin or have a standalone manifest at plugins/${dir}/plugin.json`);
|
||||||
console.error(`❌ extension ${dir}: missing plugin manifest at plugins/${dir}/plugin.json`);
|
|
||||||
hasErrors = true;
|
hasErrors = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -410,15 +425,16 @@ function validatePlugins() {
|
|||||||
return !hasErrors;
|
return !hasErrors;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run validation
|
if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
|
||||||
try {
|
try {
|
||||||
const isValid = validatePlugins();
|
const isValid = validatePlugins();
|
||||||
if (!isValid) {
|
if (!isValid) {
|
||||||
console.error("\n❌ Plugin validation failed");
|
console.error("\n❌ Plugin validation failed");
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
console.log("\n🎉 Plugin validation passed");
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Error during validation: ${error.message}`);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
console.log("\n🎉 Plugin validation passed");
|
|
||||||
} catch (error) {
|
|
||||||
console.error(`Error during validation: ${error.message}`);
|
|
||||||
process.exit(1);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
import assert from "node:assert/strict";
|
||||||
|
import { test } from "node:test";
|
||||||
|
import { isReusableExtensionRegistered } from "./validate-plugins.mjs";
|
||||||
|
|
||||||
|
test("accepts a reusable extension bundled only by a parent plugin", () => {
|
||||||
|
assert.equal(
|
||||||
|
isReusableExtensionRegistered(
|
||||||
|
"daily-focus-board",
|
||||||
|
new Set(["ember"]),
|
||||||
|
new Set(["daily-focus-board"])
|
||||||
|
),
|
||||||
|
true
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("accepts a same-named standalone extension plugin", () => {
|
||||||
|
assert.equal(
|
||||||
|
isReusableExtensionRegistered(
|
||||||
|
"daily-focus-board",
|
||||||
|
new Set(["daily-focus-board"]),
|
||||||
|
new Set()
|
||||||
|
),
|
||||||
|
true
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("rejects an orphaned reusable extension", () => {
|
||||||
|
assert.equal(
|
||||||
|
isReusableExtensionRegistered(
|
||||||
|
"daily-focus-board",
|
||||||
|
new Set(["ember"]),
|
||||||
|
new Set()
|
||||||
|
),
|
||||||
|
false
|
||||||
|
);
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user