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
+68
View File
@@ -459,6 +459,55 @@ async function validateCanvasPluginMetadata(plugin, errors, warnings, token) {
);
}
if (manifest.extenions !== undefined) {
errors.push(
`submission: plugins tagged with "canvas" must use "extensions" (found misspelled key "extenions") in "${manifestPath}"`,
);
}
if (manifest.extensions !== undefined && manifest.extensions !== "extensions") {
errors.push(
`submission: plugins tagged with "canvas" may omit "extensions", but if provided it must be "extensions" in "${manifestPath}"`,
);
}
const extensionContainerPath = joinRepoPath(pluginRoot, "extensions");
const extensionContainerResponse = await fetchGitHubFile(repo, extensionContainerPath, releaseLocator, token);
if (extensionContainerResponse.kind === "notFound") {
errors.push(
`submission: plugins tagged with "canvas" must include an "extensions" directory at ${releaseLocatorDescription}`,
);
} else if (extensionContainerResponse.kind === "apiError") {
warnings.push(
`submission: could not verify "extensions" directory in GitHub repository "${repo}" at ${releaseLocatorDescription}; a maintainer should re-run intake`,
);
} else if (
!(
extensionContainerResponse.data?.type === "dir"
|| Array.isArray(extensionContainerResponse.data)
)
) {
errors.push(
`submission: "extensions" must be a directory in ${releaseLocatorDescription}`,
);
}
const extensionEntryPath = joinRepoPath(pluginRoot, "extensions", "extension.mjs");
const extensionEntryResponse = await fetchGitHubFile(repo, extensionEntryPath, releaseLocator, token);
if (extensionEntryResponse.kind === "notFound") {
errors.push(
`submission: plugins tagged with "canvas" must include "extensions/extension.mjs" at ${releaseLocatorDescription}`,
);
} else if (extensionEntryResponse.kind === "apiError") {
warnings.push(
`submission: could not verify "extensions/extension.mjs" in GitHub repository "${repo}" at ${releaseLocatorDescription}; a maintainer should re-run intake`,
);
} else if (extensionEntryResponse.data?.type !== "file") {
errors.push(
`submission: "extensions/extension.mjs" must be a file in ${releaseLocatorDescription}`,
);
}
const previewPath = joinRepoPath(pluginRoot, EXTERNAL_CANVAS_PREVIEW_PATH);
const previewResponse = await fetchGitHubFile(repo, previewPath, releaseLocator, token);
if (previewResponse.kind === "notFound") {
@@ -580,11 +629,13 @@ function normalizeQualityGateResult(rawResult) {
vally_lint_status: "not_run",
smoke_status: "not_run",
version_match_status: "not_run",
canvas_structure_status: "not_run",
failure_class: "none",
summary: "",
vally_lint_output: "",
smoke_output: "",
version_match_output: "",
canvas_structure_output: "",
};
if (!rawResult || typeof rawResult !== "object" || Array.isArray(rawResult)) {
@@ -601,6 +652,7 @@ function buildQualityGatesCommentSection(qualityResult) {
const vallyState = qualityResult.vally_lint_status || "not_run";
const smokeState = qualityResult.smoke_status || "not_run";
const versionMatchState = qualityResult.version_match_status || "not_run";
const canvasStructureState = qualityResult.canvas_structure_status || "not_run";
const summaryText = String(qualityResult.summary || "").trim() || "_No quality gate details were provided._";
const sections = [
@@ -611,6 +663,7 @@ function buildQualityGatesCommentSection(qualityResult) {
`| vally lint | ${vallyState} |`,
`| install smoke test | ${smokeState} |`,
`| version match | ${versionMatchState} |`,
`| canvas structure | ${canvasStructureState} |`,
"",
summaryText,
];
@@ -660,6 +713,21 @@ function buildQualityGatesCommentSection(qualityResult) {
);
}
const canvasStructureOutput = String(qualityResult.canvas_structure_output || "").trim();
if (canvasStructureOutput) {
sections.push(
"",
"<details>",
"<summary>Canvas structure output</summary>",
"",
"```text",
canvasStructureOutput,
"```",
"",
"</details>",
);
}
return sections.join("\n");
}