Migrate extension plugin materialization to extensions container (#2334)

* Migrate extension plugin materialization layout

Materialize extension plugins into a dedicated extensions/ container, validate the new manifest convention, and bump extension plugin versions.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: d26008fb-9928-4ba7-b7c8-36f35320f7c1

* Keep extension manifests source-compatible

Restore source extension manifests to "extensions": "." while preserving materialization-time rewrite to "extensions" in distribution output.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: d26008fb-9928-4ba7-b7c8-36f35320f7c1

* Validate canvas extension layout for external submissions

Add intake and quality-gate checks for canvas-tagged external plugins so they must include extensions/extension.mjs and optional manifest extensions is validated when present.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: d26008fb-9928-4ba7-b7c8-36f35320f7c1

* Fix plugin clean extension pass and typo guard text

Declare EXTENSIONS_DIR in clean-materialized-plugins and run extension cleanup once after plugin cleanup. Also normalize misspelled-key detection strings to satisfy spelling checks without changing validation behavior.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: d26008fb-9928-4ba7-b7c8-36f35320f7c1

* Proper codespell fix

* Separate canvas structure quality gate status

Track canvas structure as its own gate status and output, include it in aggregate summaries, and enforce Git object types so extensions/ is a tree and extensions/extension.mjs is a blob.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: d26008fb-9928-4ba7-b7c8-36f35320f7c1

* Potential fix for pull request finding

Co-authored-by: Copilot Autofix powered by AI <175728472+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-07-17 18:04:03 +10:00
committed by GitHub
parent 38ab1360a4
commit 40665c23b7
32 changed files with 583 additions and 49 deletions
+44
View File
@@ -6,6 +6,7 @@ import { fileURLToPath } from "url";
import { ROOT_FOLDER } from "./constants.mjs";
const PLUGINS_DIR = path.join(ROOT_FOLDER, "plugins");
const EXTENSIONS_DIR = path.join(ROOT_FOLDER, "extensions");
const MATERIALIZED_SPECS = {
agents: {
path: "agents",
@@ -89,6 +90,30 @@ function cleanPlugin(pluginPath) {
return { removed, manifestUpdated };
}
function cleanMaterializedExtensionPlugin(extensionPath) {
const pluginJsonPath = path.join(extensionPath, ".github", "plugin", "plugin.json");
let manifestUpdated = false;
if (fs.existsSync(pluginJsonPath)) {
const plugin = JSON.parse(fs.readFileSync(pluginJsonPath, "utf8"));
if (plugin.extensions === "extensions") {
plugin.extensions = ".";
fs.writeFileSync(pluginJsonPath, JSON.stringify(plugin, null, 2) + "\n", "utf8");
manifestUpdated = true;
console.log(` Updated ${path.basename(extensionPath)}/.github/plugin/plugin.json`);
}
}
const target = path.join(extensionPath, "extensions");
if (!fs.existsSync(target) || !fs.statSync(target).isDirectory()) {
return { removed: 0, manifestUpdated };
}
const count = countFiles(target);
fs.rmSync(target, { recursive: true, force: true });
console.log(` Removed ${path.basename(extensionPath)}/extensions/ (${count} files)`);
return { removed: count, manifestUpdated };
}
function countFiles(dir) {
let count = 0;
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
@@ -171,6 +196,25 @@ function main() {
}
}
if (fs.existsSync(EXTENSIONS_DIR)) {
const extensionDirs = fs.readdirSync(EXTENSIONS_DIR, { withFileTypes: true })
.filter((entry) => entry.isDirectory())
.map((entry) => entry.name)
.sort();
for (const dirName of extensionDirs) {
const extensionPath = path.join(EXTENSIONS_DIR, dirName);
if (!fs.existsSync(path.join(extensionPath, "extension.mjs"))) {
continue;
}
const { removed, manifestUpdated } = cleanMaterializedExtensionPlugin(extensionPath);
total += removed;
if (manifestUpdated) {
manifestsUpdated++;
}
}
}
console.log();
if (total === 0 && manifestsUpdated === 0) {
console.log("✅ No materialized files found. Plugins are already clean.");