mirror of
https://github.com/github/awesome-copilot.git
synced 2026-07-18 11:50:00 +00:00
Fix extension materialization to move bundles into container (#2339)
* 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 * Move extension bundles during materialization Change extension-plugin materialization to move root bundle entries into extensions/ instead of copying them, and assert originals are removed in test coverage. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: d26008fb-9928-4ba7-b7c8-36f35320f7c1 * Nest materialized extension bundles by plugin name Materialize extension plugins into extensions/<plugin-name>/... (moved entries) so resulting paths are duplicated by design, and bump extension plugin versions to the next patch release. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: d26008fb-9928-4ba7-b7c8-36f35320f7c1 * Fix extension materialization publish and cleanup Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: d26008fb-9928-4ba7-b7c8-36f35320f7c1 * Preserve root extension logo asset Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: d26008fb-9928-4ba7-b7c8-36f35320f7c1
This commit is contained in:
@@ -13,7 +13,7 @@
|
|||||||
"name": "accessibility-kanban",
|
"name": "accessibility-kanban",
|
||||||
"source": "extensions/accessibility-kanban",
|
"source": "extensions/accessibility-kanban",
|
||||||
"description": "Kanban board to manage accessibility issues, allow you to plan, track, and complete remediation work.",
|
"description": "Kanban board to manage accessibility issues, allow you to plan, track, and complete remediation work.",
|
||||||
"version": "1.0.1"
|
"version": "1.0.2"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "acreadiness-cockpit",
|
"name": "acreadiness-cockpit",
|
||||||
@@ -85,13 +85,13 @@
|
|||||||
"name": "apng-studio",
|
"name": "apng-studio",
|
||||||
"source": "extensions/apng-studio",
|
"source": "extensions/apng-studio",
|
||||||
"description": "Interactive GitHub Copilot app canvas extension for building Animated PNG (APNG) files from frames. Draw or upload frames, tune per-frame timing and compositing, preview live, send the result to your phone by QR, and export an animated .png.",
|
"description": "Interactive GitHub Copilot app canvas extension for building Animated PNG (APNG) files from frames. Draw or upload frames, tune per-frame timing and compositing, preview live, send the result to your phone by QR, and export an animated .png.",
|
||||||
"version": "1.0.1"
|
"version": "1.0.2"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "arcade-canvas",
|
"name": "arcade-canvas",
|
||||||
"source": "extensions/arcade-canvas",
|
"source": "extensions/arcade-canvas",
|
||||||
"description": "Play five retro Phaser mini-games in a Copilot canvas while agents work.",
|
"description": "Play five retro Phaser mini-games in a Copilot canvas while agents work.",
|
||||||
"version": "1.0.1"
|
"version": "1.0.2"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "arch",
|
"name": "arch",
|
||||||
@@ -158,7 +158,7 @@
|
|||||||
"name": "backlog-swipe-triage",
|
"name": "backlog-swipe-triage",
|
||||||
"source": "extensions/backlog-swipe-triage",
|
"source": "extensions/backlog-swipe-triage",
|
||||||
"description": "Quickly swipe through backlog issues to triage decisions like assign, needs-info, defer, close, or ignore.",
|
"description": "Quickly swipe through backlog issues to triage decisions like assign, needs-info, defer, close, or ignore.",
|
||||||
"version": "1.0.1"
|
"version": "1.0.2"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "cast-imaging",
|
"name": "cast-imaging",
|
||||||
@@ -195,7 +195,7 @@
|
|||||||
"name": "chromium-control-canvas",
|
"name": "chromium-control-canvas",
|
||||||
"source": "extensions/chromium-control-canvas",
|
"source": "extensions/chromium-control-canvas",
|
||||||
"description": "Opens a real Chromium window you can navigate and interact with from a Copilot canvas control panel and agent actions.",
|
"description": "Opens a real Chromium window you can navigate and interact with from a Copilot canvas control panel and agent actions.",
|
||||||
"version": "1.0.1"
|
"version": "1.0.2"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "clojure-interactive-programming",
|
"name": "clojure-interactive-programming",
|
||||||
@@ -239,13 +239,13 @@
|
|||||||
"name": "color-orb",
|
"name": "color-orb",
|
||||||
"source": "extensions/color-orb",
|
"source": "extensions/color-orb",
|
||||||
"description": "A visual orb that users can ask the agent to recolor while showing a live activity log in the canvas.",
|
"description": "A visual orb that users can ask the agent to recolor while showing a live activity log in the canvas.",
|
||||||
"version": "1.0.1"
|
"version": "1.0.2"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "connector-namespaces",
|
"name": "connector-namespaces",
|
||||||
"source": "extensions/connector-namespaces",
|
"source": "extensions/connector-namespaces",
|
||||||
"description": "Browse, connect, and open MCP connectors from an Azure Connector Namespace.",
|
"description": "Browse, connect, and open MCP connectors from an Azure Connector Namespace.",
|
||||||
"version": "1.1.1"
|
"version": "1.1.2"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "context-engineering",
|
"name": "context-engineering",
|
||||||
@@ -373,7 +373,7 @@
|
|||||||
"name": "diagram-viewer",
|
"name": "diagram-viewer",
|
||||||
"source": "extensions/diagram-viewer",
|
"source": "extensions/diagram-viewer",
|
||||||
"description": "Render diagrams, click nodes to drill down, and view agent-generated explanations directly in the canvas.",
|
"description": "Render diagrams, click nodes to drill down, and view agent-generated explanations directly in the canvas.",
|
||||||
"version": "1.0.1"
|
"version": "1.0.2"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "dotnet",
|
"name": "dotnet",
|
||||||
@@ -520,7 +520,7 @@
|
|||||||
"name": "feedback-themes",
|
"name": "feedback-themes",
|
||||||
"source": "extensions/feedback-themes",
|
"source": "extensions/feedback-themes",
|
||||||
"description": "Explore grouped customer feedback signals by impact and drill into a theme to guide product next steps.",
|
"description": "Explore grouped customer feedback signals by impact and drill into a theme to guide product next steps.",
|
||||||
"version": "1.0.1"
|
"version": "1.0.2"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "figma",
|
"name": "figma",
|
||||||
@@ -593,7 +593,7 @@
|
|||||||
"name": "gesture-review",
|
"name": "gesture-review",
|
||||||
"source": "extensions/gesture-review",
|
"source": "extensions/gesture-review",
|
||||||
"description": "Review pull requests with a live camera feed and approve or reject using thumbs-up/thumbs-down gestures.",
|
"description": "Review pull requests with a live camera feed and approve or reject using thumbs-up/thumbs-down gestures.",
|
||||||
"version": "1.0.1"
|
"version": "1.0.2"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "gh-skills-builder",
|
"name": "gh-skills-builder",
|
||||||
@@ -701,7 +701,7 @@
|
|||||||
"name": "java-modernization-studio",
|
"name": "java-modernization-studio",
|
||||||
"source": "extensions/java-modernization-studio",
|
"source": "extensions/java-modernization-studio",
|
||||||
"description": "Drive the GitHub Copilot App Modernization for Java workflow from an interactive canvas: environment readiness, repo assessment, prioritized plan and progress, validation gates, and one-click predefined-task runs grounded in the repo's real artifacts.",
|
"description": "Drive the GitHub Copilot App Modernization for Java workflow from an interactive canvas: environment readiness, repo assessment, prioritized plan and progress, validation gates, and one-click predefined-task runs grounded in the repo's real artifacts.",
|
||||||
"version": "1.0.1"
|
"version": "1.0.2"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "kotlin-mcp-development",
|
"name": "kotlin-mcp-development",
|
||||||
@@ -980,13 +980,13 @@
|
|||||||
"name": "release-notes-showcase",
|
"name": "release-notes-showcase",
|
||||||
"source": "extensions/release-notes-showcase",
|
"source": "extensions/release-notes-showcase",
|
||||||
"description": "Compose and refine launch-ready release notes with contributor callouts and export-friendly output.",
|
"description": "Compose and refine launch-ready release notes with contributor callouts and export-friendly output.",
|
||||||
"version": "1.0.1"
|
"version": "1.0.2"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "repo-actions-hub",
|
"name": "repo-actions-hub",
|
||||||
"source": "extensions/repo-actions-hub",
|
"source": "extensions/repo-actions-hub",
|
||||||
"description": "Browse repository GitHub Actions workflows, inspect recent runs, and trigger manual workflow_dispatch runs from a Copilot canvas.",
|
"description": "Browse repository GitHub Actions workflows, inspect recent runs, and trigger manual workflow_dispatch runs from a Copilot canvas.",
|
||||||
"version": "1.0.1"
|
"version": "1.0.2"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "roundup",
|
"name": "roundup",
|
||||||
@@ -1028,7 +1028,7 @@
|
|||||||
"name": "site-studio",
|
"name": "site-studio",
|
||||||
"source": "extensions/site-studio",
|
"source": "extensions/site-studio",
|
||||||
"description": "Plan, draft, and track a personal website section by section — a shared canvas where you and your agent author content, watch progress, and review every change.",
|
"description": "Plan, draft, and track a personal website section by section — a shared canvas where you and your agent author content, watch progress, and review every change.",
|
||||||
"version": "1.0.1"
|
"version": "1.0.2"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "skill-image-gen",
|
"name": "skill-image-gen",
|
||||||
@@ -1118,13 +1118,13 @@
|
|||||||
"name": "tiny-tool-town-submitter",
|
"name": "tiny-tool-town-submitter",
|
||||||
"source": "extensions/tiny-tool-town-submitter",
|
"source": "extensions/tiny-tool-town-submitter",
|
||||||
"description": "Inspect a repository, improve Tiny Tool Town readiness, submit its listing issue, and launch remediation work.",
|
"description": "Inspect a repository, improve Tiny Tool Town readiness, submit its listing issue, and launch remediation work.",
|
||||||
"version": "1.0.1"
|
"version": "1.0.2"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "token-pacman",
|
"name": "token-pacman",
|
||||||
"source": "extensions/token-pacman",
|
"source": "extensions/token-pacman",
|
||||||
"description": "Visualizes live session AI-credit usage as a Pac-Man board with pellets, ghosts, fruit milestones, and game-over limits.",
|
"description": "Visualizes live session AI-credit usage as a Pac-Man board with pellets, ghosts, fruit milestones, and game-over limits.",
|
||||||
"version": "1.0.1"
|
"version": "1.0.2"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "typescript-mcp-development",
|
"name": "typescript-mcp-development",
|
||||||
@@ -1335,7 +1335,7 @@
|
|||||||
"name": "where-was-i",
|
"name": "where-was-i",
|
||||||
"source": "extensions/where-was-i",
|
"source": "extensions/where-was-i",
|
||||||
"description": "Reconstruct your dev context (branch, commits, uncommitted work, PR clues) and trigger a resume prompt to continue quickly.",
|
"description": "Reconstruct your dev context (branch, commits, uncommitted work, PR clues) and trigger a resume prompt to continue quickly.",
|
||||||
"version": "1.0.1"
|
"version": "1.0.2"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "winappcli",
|
"name": "winappcli",
|
||||||
@@ -1399,7 +1399,7 @@
|
|||||||
"name": "work-hub",
|
"name": "work-hub",
|
||||||
"source": "extensions/work-hub",
|
"source": "extensions/work-hub",
|
||||||
"description": "Generic cross-repo command center canvas for GitHub Copilot with onboarding, focus planning, repo health, work signals, and session cleanup.",
|
"description": "Generic cross-repo command center canvas for GitHub Copilot with onboarding, focus planning, repo health, work signals, and session cleanup.",
|
||||||
"version": "1.0.1"
|
"version": "1.0.2"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,6 +28,41 @@ const MATERIALIZED_SPECS = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function copyDirRecursive(src, dest) {
|
||||||
|
fs.mkdirSync(dest, { recursive: true });
|
||||||
|
for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
|
||||||
|
const srcPath = path.join(src, entry.name);
|
||||||
|
const destPath = path.join(dest, entry.name);
|
||||||
|
if (entry.isDirectory()) {
|
||||||
|
copyDirRecursive(srcPath, destPath);
|
||||||
|
} else {
|
||||||
|
fs.copyFileSync(srcPath, destPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function moveEntry(srcPath, destPath) {
|
||||||
|
fs.mkdirSync(path.dirname(destPath), { recursive: true });
|
||||||
|
try {
|
||||||
|
fs.renameSync(srcPath, destPath);
|
||||||
|
return;
|
||||||
|
} catch (error) {
|
||||||
|
if (!["EXDEV", "EEXIST", "ENOTEMPTY", "EPERM"].includes(error?.code)) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const stats = fs.statSync(srcPath);
|
||||||
|
if (stats.isDirectory()) {
|
||||||
|
copyDirRecursive(srcPath, destPath);
|
||||||
|
fs.rmSync(srcPath, { recursive: true, force: true });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
fs.copyFileSync(srcPath, destPath);
|
||||||
|
fs.rmSync(srcPath, { force: true });
|
||||||
|
}
|
||||||
|
|
||||||
export function restoreManifestFromMaterializedFiles(pluginPath) {
|
export function restoreManifestFromMaterializedFiles(pluginPath) {
|
||||||
const pluginJsonPath = path.join(pluginPath, ".github/plugin", "plugin.json");
|
const pluginJsonPath = path.join(pluginPath, ".github/plugin", "plugin.json");
|
||||||
if (!fs.existsSync(pluginJsonPath)) {
|
if (!fs.existsSync(pluginJsonPath)) {
|
||||||
@@ -90,15 +125,22 @@ function cleanPlugin(pluginPath) {
|
|||||||
return { removed, manifestUpdated };
|
return { removed, manifestUpdated };
|
||||||
}
|
}
|
||||||
|
|
||||||
function cleanMaterializedExtensionPlugin(extensionPath) {
|
export function cleanMaterializedExtensionPlugin(extensionPath) {
|
||||||
const pluginJsonPath = path.join(extensionPath, ".github", "plugin", "plugin.json");
|
const pluginJsonPath = path.join(extensionPath, ".github", "plugin", "plugin.json");
|
||||||
let manifestUpdated = false;
|
let manifestUpdated = false;
|
||||||
if (fs.existsSync(pluginJsonPath)) {
|
if (fs.existsSync(pluginJsonPath)) {
|
||||||
const plugin = JSON.parse(fs.readFileSync(pluginJsonPath, "utf8"));
|
const plugin = JSON.parse(fs.readFileSync(pluginJsonPath, "utf8"));
|
||||||
|
const extensionBundlePrefix = `extensions/${path.basename(extensionPath)}/`;
|
||||||
if (plugin.extensions === "extensions") {
|
if (plugin.extensions === "extensions") {
|
||||||
plugin.extensions = ".";
|
plugin.extensions = ".";
|
||||||
fs.writeFileSync(pluginJsonPath, JSON.stringify(plugin, null, 2) + "\n", "utf8");
|
|
||||||
manifestUpdated = true;
|
manifestUpdated = true;
|
||||||
|
}
|
||||||
|
if (typeof plugin.logo === "string" && plugin.logo.startsWith(extensionBundlePrefix)) {
|
||||||
|
plugin.logo = plugin.logo.slice(extensionBundlePrefix.length);
|
||||||
|
manifestUpdated = true;
|
||||||
|
}
|
||||||
|
if (manifestUpdated) {
|
||||||
|
fs.writeFileSync(pluginJsonPath, JSON.stringify(plugin, null, 2) + "\n", "utf8");
|
||||||
console.log(` Updated ${path.basename(extensionPath)}/.github/plugin/plugin.json`);
|
console.log(` Updated ${path.basename(extensionPath)}/.github/plugin/plugin.json`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -108,12 +150,43 @@ function cleanMaterializedExtensionPlugin(extensionPath) {
|
|||||||
return { removed: 0, manifestUpdated };
|
return { removed: 0, manifestUpdated };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const bundleRoot = path.join(target, path.basename(extensionPath));
|
||||||
const count = countFiles(target);
|
const count = countFiles(target);
|
||||||
|
if (fs.existsSync(bundleRoot) && fs.statSync(bundleRoot).isDirectory()) {
|
||||||
|
for (const entry of fs.readdirSync(bundleRoot, { withFileTypes: true })) {
|
||||||
|
moveEntry(path.join(bundleRoot, entry.name), path.join(extensionPath, entry.name));
|
||||||
|
}
|
||||||
|
console.log(` Restored ${path.basename(extensionPath)}/ from materialized extensions bundle`);
|
||||||
|
}
|
||||||
|
|
||||||
fs.rmSync(target, { recursive: true, force: true });
|
fs.rmSync(target, { recursive: true, force: true });
|
||||||
console.log(` Removed ${path.basename(extensionPath)}/extensions/ (${count} files)`);
|
console.log(` Removed ${path.basename(extensionPath)}/extensions/ (${count} files)`);
|
||||||
return { removed: count, manifestUpdated };
|
return { removed: count, manifestUpdated };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isExtensionPluginDirectory(extensionPath) {
|
||||||
|
if (fs.existsSync(path.join(extensionPath, "extension.mjs"))) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const bundleEntry = path.join(extensionPath, "extensions", path.basename(extensionPath), "extension.mjs");
|
||||||
|
if (fs.existsSync(bundleEntry)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const pluginJsonPath = path.join(extensionPath, ".github", "plugin", "plugin.json");
|
||||||
|
if (!fs.existsSync(pluginJsonPath)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const plugin = JSON.parse(fs.readFileSync(pluginJsonPath, "utf8"));
|
||||||
|
return plugin.extensions === "extensions";
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function countFiles(dir) {
|
function countFiles(dir) {
|
||||||
let count = 0;
|
let count = 0;
|
||||||
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
||||||
@@ -204,7 +277,7 @@ function main() {
|
|||||||
|
|
||||||
for (const dirName of extensionDirs) {
|
for (const dirName of extensionDirs) {
|
||||||
const extensionPath = path.join(EXTENSIONS_DIR, dirName);
|
const extensionPath = path.join(EXTENSIONS_DIR, dirName);
|
||||||
if (!fs.existsSync(path.join(extensionPath, "extension.mjs"))) {
|
if (!isExtensionPluginDirectory(extensionPath)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const { removed, manifestUpdated } = cleanMaterializedExtensionPlugin(extensionPath);
|
const { removed, manifestUpdated } = cleanMaterializedExtensionPlugin(extensionPath);
|
||||||
|
|||||||
@@ -62,6 +62,16 @@ function collectLocalPluginsFromRoot(rootDir, sourcePrefix, includeEntry = () =>
|
|||||||
return plugins;
|
return plugins;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function hasExtensionEntryPoint(extensionDir, extensionName) {
|
||||||
|
const candidateEntryPoints = [
|
||||||
|
path.join(extensionDir, "extension.mjs"),
|
||||||
|
path.join(extensionDir, "extensions", "extension.mjs"),
|
||||||
|
path.join(extensionDir, "extensions", extensionName, "extension.mjs"),
|
||||||
|
];
|
||||||
|
|
||||||
|
return candidateEntryPoints.some((entryPointPath) => fs.existsSync(entryPointPath));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate marketplace.json from plugin directories
|
* Generate marketplace.json from plugin directories
|
||||||
*/
|
*/
|
||||||
@@ -78,7 +88,7 @@ function generateMarketplace() {
|
|||||||
...collectLocalPluginsFromRoot(
|
...collectLocalPluginsFromRoot(
|
||||||
EXTENSIONS_DIR,
|
EXTENSIONS_DIR,
|
||||||
"extensions",
|
"extensions",
|
||||||
(entryName) => fs.existsSync(path.join(EXTENSIONS_DIR, entryName, "extension.mjs"))
|
(entryName) => hasExtensionEntryPoint(path.join(EXTENSIONS_DIR, entryName), entryName)
|
||||||
)
|
)
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@@ -35,6 +35,16 @@ const WEBSITE_SOURCE_DATA_DIR = path.join(WEBSITE_DIR, "data");
|
|||||||
const EXTERNAL_CANVAS_KEYWORD = "canvas";
|
const EXTERNAL_CANVAS_KEYWORD = "canvas";
|
||||||
const EXTERNAL_CANVAS_PREVIEW_PATH = "assets/preview.png";
|
const EXTERNAL_CANVAS_PREVIEW_PATH = "assets/preview.png";
|
||||||
|
|
||||||
|
function hasExtensionEntryPoint(extensionDir, extensionName) {
|
||||||
|
const candidateEntryPoints = [
|
||||||
|
path.join(extensionDir, "extension.mjs"),
|
||||||
|
path.join(extensionDir, "extensions", "extension.mjs"),
|
||||||
|
path.join(extensionDir, "extensions", extensionName, "extension.mjs"),
|
||||||
|
];
|
||||||
|
|
||||||
|
return candidateEntryPoints.some((entryPointPath) => fs.existsSync(entryPointPath));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ensure the output directory exists
|
* Ensure the output directory exists
|
||||||
*/
|
*/
|
||||||
@@ -544,7 +554,7 @@ function generatePluginsData(gitDates, resourceIndex = {}) {
|
|||||||
const extensionDirs = fs.readdirSync(EXTENSIONS_DIR, { withFileTypes: true })
|
const extensionDirs = fs.readdirSync(EXTENSIONS_DIR, { withFileTypes: true })
|
||||||
.filter((entry) => {
|
.filter((entry) => {
|
||||||
if (!entry.isDirectory()) return false;
|
if (!entry.isDirectory()) return false;
|
||||||
return fs.existsSync(path.join(EXTENSIONS_DIR, entry.name, "extension.mjs"));
|
return hasExtensionEntryPoint(path.join(EXTENSIONS_DIR, entry.name), entry.name);
|
||||||
})
|
})
|
||||||
.map((entry) => entry.name)
|
.map((entry) => entry.name)
|
||||||
.sort((a, b) => a.localeCompare(b));
|
.sort((a, b) => a.localeCompare(b));
|
||||||
@@ -1235,12 +1245,7 @@ function generateCanvasManifest(gitDates, commitSha) {
|
|||||||
.readdirSync(EXTENSIONS_DIR, { withFileTypes: true })
|
.readdirSync(EXTENSIONS_DIR, { withFileTypes: true })
|
||||||
.filter((entry) => {
|
.filter((entry) => {
|
||||||
if (!entry.isDirectory()) return false;
|
if (!entry.isDirectory()) return false;
|
||||||
const extensionEntryPoint = path.join(
|
return hasExtensionEntryPoint(path.join(EXTENSIONS_DIR, entry.name), entry.name);
|
||||||
EXTENSIONS_DIR,
|
|
||||||
entry.name,
|
|
||||||
"extension.mjs"
|
|
||||||
);
|
|
||||||
return fs.existsSync(extensionEntryPoint);
|
|
||||||
})
|
})
|
||||||
.sort((a, b) => a.name.localeCompare(b.name));
|
.sort((a, b) => a.name.localeCompare(b.name));
|
||||||
|
|
||||||
|
|||||||
+41
-11
@@ -24,15 +24,34 @@ function copyDirRecursive(src, dest) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function copyEntryRecursive(srcPath, destPath) {
|
function moveEntry(srcPath, destPath) {
|
||||||
|
fs.mkdirSync(path.dirname(destPath), { recursive: true });
|
||||||
|
try {
|
||||||
|
fs.renameSync(srcPath, destPath);
|
||||||
|
return;
|
||||||
|
} catch (error) {
|
||||||
|
if (error?.code !== "EXDEV") {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const stats = fs.statSync(srcPath);
|
const stats = fs.statSync(srcPath);
|
||||||
if (stats.isDirectory()) {
|
if (stats.isDirectory()) {
|
||||||
copyDirRecursive(srcPath, destPath);
|
copyDirRecursive(srcPath, destPath);
|
||||||
|
fs.rmSync(srcPath, { recursive: true, force: true });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
fs.mkdirSync(path.dirname(destPath), { recursive: true });
|
|
||||||
fs.copyFileSync(srcPath, destPath);
|
fs.copyFileSync(srcPath, destPath);
|
||||||
|
fs.rmSync(srcPath, { force: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
function isRelativeAssetPath(assetPath) {
|
||||||
|
return typeof assetPath === "string" &&
|
||||||
|
assetPath.length > 0 &&
|
||||||
|
!/^(?:[a-z][a-z0-9+.-]*:)?\/\//i.test(assetPath) &&
|
||||||
|
!assetPath.startsWith("data:") &&
|
||||||
|
!path.isAbsolute(assetPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -61,7 +80,7 @@ function resolveSource(relPath) {
|
|||||||
export function materializeExtensionPlugin(extensionPath) {
|
export function materializeExtensionPlugin(extensionPath) {
|
||||||
const pluginJsonPath = path.join(extensionPath, ".github", "plugin", "plugin.json");
|
const pluginJsonPath = path.join(extensionPath, ".github", "plugin", "plugin.json");
|
||||||
if (!fs.existsSync(pluginJsonPath)) {
|
if (!fs.existsSync(pluginJsonPath)) {
|
||||||
return { copiedEntries: 0, manifestUpdated: false, skipped: true };
|
return { movedEntries: 0, manifestUpdated: false, skipped: true };
|
||||||
}
|
}
|
||||||
|
|
||||||
let metadata;
|
let metadata;
|
||||||
@@ -72,20 +91,31 @@ export function materializeExtensionPlugin(extensionPath) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const extensionContainerPath = path.join(extensionPath, "extensions");
|
const extensionContainerPath = path.join(extensionPath, "extensions");
|
||||||
|
const extensionBundlePath = path.join(extensionContainerPath, path.basename(extensionPath));
|
||||||
fs.rmSync(extensionContainerPath, { recursive: true, force: true });
|
fs.rmSync(extensionContainerPath, { recursive: true, force: true });
|
||||||
fs.mkdirSync(extensionContainerPath, { recursive: true });
|
fs.mkdirSync(extensionBundlePath, { recursive: true });
|
||||||
|
|
||||||
let copiedEntries = 0;
|
let movedEntries = 0;
|
||||||
for (const entry of fs.readdirSync(extensionPath, { withFileTypes: true })) {
|
for (const entry of fs.readdirSync(extensionPath, { withFileTypes: true })) {
|
||||||
if (entry.name === ".github" || entry.name === "extensions") {
|
if (entry.name === ".github" || entry.name === "extensions") {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
copyEntryRecursive(
|
moveEntry(
|
||||||
path.join(extensionPath, entry.name),
|
path.join(extensionPath, entry.name),
|
||||||
path.join(extensionContainerPath, entry.name)
|
path.join(extensionBundlePath, entry.name)
|
||||||
);
|
);
|
||||||
copiedEntries++;
|
movedEntries++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isRelativeAssetPath(metadata.logo)) {
|
||||||
|
const normalizedLogoPath = metadata.logo.replace(/\\/g, "/").replace(/^\.\//, "");
|
||||||
|
const bundledLogoPath = path.join(extensionBundlePath, normalizedLogoPath);
|
||||||
|
if (fs.existsSync(bundledLogoPath)) {
|
||||||
|
const rootLogoPath = path.join(extensionPath, normalizedLogoPath);
|
||||||
|
fs.mkdirSync(path.dirname(rootLogoPath), { recursive: true });
|
||||||
|
fs.copyFileSync(bundledLogoPath, rootLogoPath);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let manifestUpdated = false;
|
let manifestUpdated = false;
|
||||||
@@ -97,7 +127,7 @@ export function materializeExtensionPlugin(extensionPath) {
|
|||||||
fs.writeFileSync(pluginJsonPath, JSON.stringify(metadata, null, 2) + "\n", "utf8");
|
fs.writeFileSync(pluginJsonPath, JSON.stringify(metadata, null, 2) + "\n", "utf8");
|
||||||
}
|
}
|
||||||
|
|
||||||
return { copiedEntries, manifestUpdated, skipped: false };
|
return { movedEntries, manifestUpdated, skipped: false };
|
||||||
}
|
}
|
||||||
|
|
||||||
function materializePlugins() {
|
function materializePlugins() {
|
||||||
@@ -261,8 +291,8 @@ function materializePlugins() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
totalExtensionPlugins++;
|
totalExtensionPlugins++;
|
||||||
totalExtensionPluginEntries += result.copiedEntries;
|
totalExtensionPluginEntries += result.movedEntries;
|
||||||
console.log(`✓ ${dirName}: materialized extension bundle into ./extensions (${result.copiedEntries} entries)`);
|
console.log(`✓ ${dirName}: materialized extension bundle into ./extensions (${result.movedEntries} entries)`);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(`Error: Failed to materialize extension plugin ${dirName}: ${err.message}`);
|
console.error(`Error: Failed to materialize extension plugin ${dirName}: ${err.message}`);
|
||||||
errors++;
|
errors++;
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import os from "os";
|
|||||||
import path from "path";
|
import path from "path";
|
||||||
import { after, test } from "node:test";
|
import { after, test } from "node:test";
|
||||||
import { materializeExtensionPlugin } from "./materialize-plugins.mjs";
|
import { materializeExtensionPlugin } from "./materialize-plugins.mjs";
|
||||||
|
import { cleanMaterializedExtensionPlugin } from "./clean-materialized-plugins.mjs";
|
||||||
|
|
||||||
const tempDirs = [];
|
const tempDirs = [];
|
||||||
|
|
||||||
@@ -13,7 +14,7 @@ after(() => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
test("materializeExtensionPlugin writes extension bundles to ./extensions and rewrites manifest", () => {
|
test("materializeExtensionPlugin writes extension bundles to ./extensions and preserves root logo assets", () => {
|
||||||
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "materialize-extension-plugin-"));
|
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "materialize-extension-plugin-"));
|
||||||
tempDirs.push(tempDir);
|
tempDirs.push(tempDir);
|
||||||
|
|
||||||
@@ -32,17 +33,57 @@ test("materializeExtensionPlugin writes extension bundles to ./extensions and re
|
|||||||
fs.writeFileSync(path.join(pluginDir, "assets", "preview.png"), "fake-image-bytes");
|
fs.writeFileSync(path.join(pluginDir, "assets", "preview.png"), "fake-image-bytes");
|
||||||
|
|
||||||
const result = materializeExtensionPlugin(pluginDir);
|
const result = materializeExtensionPlugin(pluginDir);
|
||||||
|
const bundleRoot = path.join(pluginDir, "extensions", "extension-plugin");
|
||||||
|
|
||||||
assert.equal(result.skipped, false);
|
assert.equal(result.skipped, false);
|
||||||
assert.equal(result.manifestUpdated, true);
|
assert.equal(result.manifestUpdated, true);
|
||||||
assert.equal(result.copiedEntries, 3);
|
assert.equal(result.movedEntries, 3);
|
||||||
assert.equal(fs.existsSync(path.join(pluginDir, "extensions", "extension.mjs")), true);
|
assert.equal(fs.existsSync(path.join(bundleRoot, "extension.mjs")), true);
|
||||||
assert.equal(fs.existsSync(path.join(pluginDir, "extensions", "assets", "preview.png")), true);
|
assert.equal(fs.existsSync(path.join(bundleRoot, "assets", "preview.png")), true);
|
||||||
assert.equal(fs.existsSync(path.join(pluginDir, "extensions", "README.md")), true);
|
assert.equal(fs.existsSync(path.join(bundleRoot, "README.md")), true);
|
||||||
assert.equal(fs.existsSync(path.join(pluginDir, "extensions", ".github")), false);
|
assert.equal(fs.existsSync(path.join(pluginDir, "extensions", ".github")), false);
|
||||||
|
assert.equal(fs.existsSync(path.join(pluginDir, "extension.mjs")), false);
|
||||||
|
assert.equal(fs.existsSync(path.join(pluginDir, "README.md")), false);
|
||||||
|
assert.equal(fs.existsSync(path.join(pluginDir, "assets", "preview.png")), true);
|
||||||
|
|
||||||
const pluginManifest = JSON.parse(
|
const pluginManifest = JSON.parse(
|
||||||
fs.readFileSync(path.join(pluginDir, ".github", "plugin", "plugin.json"), "utf8")
|
fs.readFileSync(path.join(pluginDir, ".github", "plugin", "plugin.json"), "utf8")
|
||||||
);
|
);
|
||||||
assert.equal(pluginManifest.extensions, "extensions");
|
assert.equal(pluginManifest.extensions, "extensions");
|
||||||
|
assert.equal(pluginManifest.logo, "assets/preview.png");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("cleanMaterializedExtensionPlugin restores moved extension files to root", () => {
|
||||||
|
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "clean-materialized-extension-plugin-"));
|
||||||
|
tempDirs.push(tempDir);
|
||||||
|
|
||||||
|
const pluginDir = path.join(tempDir, "extension-plugin");
|
||||||
|
fs.mkdirSync(path.join(pluginDir, ".github", "plugin"), { recursive: true });
|
||||||
|
fs.mkdirSync(path.join(pluginDir, "assets"), { recursive: true });
|
||||||
|
fs.writeFileSync(path.join(pluginDir, ".github", "plugin", "plugin.json"), JSON.stringify({
|
||||||
|
name: "test-extension-plugin",
|
||||||
|
description: "test plugin",
|
||||||
|
version: "1.0.0",
|
||||||
|
logo: "assets/preview.png",
|
||||||
|
extensions: ".",
|
||||||
|
}, null, 2));
|
||||||
|
fs.writeFileSync(path.join(pluginDir, "extension.mjs"), "export default {};\n");
|
||||||
|
fs.writeFileSync(path.join(pluginDir, "README.md"), "# test\n");
|
||||||
|
fs.writeFileSync(path.join(pluginDir, "assets", "preview.png"), "fake-image-bytes");
|
||||||
|
|
||||||
|
materializeExtensionPlugin(pluginDir);
|
||||||
|
const result = cleanMaterializedExtensionPlugin(pluginDir);
|
||||||
|
|
||||||
|
assert.equal(result.removed, 3);
|
||||||
|
assert.equal(result.manifestUpdated, true);
|
||||||
|
assert.equal(fs.existsSync(path.join(pluginDir, "extension.mjs")), true);
|
||||||
|
assert.equal(fs.existsSync(path.join(pluginDir, "README.md")), true);
|
||||||
|
assert.equal(fs.existsSync(path.join(pluginDir, "assets", "preview.png")), true);
|
||||||
|
assert.equal(fs.existsSync(path.join(pluginDir, "extensions")), false);
|
||||||
|
|
||||||
|
const pluginManifest = JSON.parse(
|
||||||
|
fs.readFileSync(path.join(pluginDir, ".github", "plugin", "plugin.json"), "utf8")
|
||||||
|
);
|
||||||
|
assert.equal(pluginManifest.extensions, ".");
|
||||||
|
assert.equal(pluginManifest.logo, "assets/preview.png");
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "accessibility-kanban",
|
"name": "accessibility-kanban",
|
||||||
"description": "Kanban board to manage accessibility issues, allow you to plan, track, and complete remediation work.",
|
"description": "Kanban board to manage accessibility issues, allow you to plan, track, and complete remediation work.",
|
||||||
"version": "1.0.1",
|
"version": "1.0.2",
|
||||||
"author": {
|
"author": {
|
||||||
"name": "Aaron Powell",
|
"name": "Aaron Powell",
|
||||||
"url": "https://github.com/aaronpowell"
|
"url": "https://github.com/aaronpowell"
|
||||||
|
|||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "apng-studio",
|
"name": "apng-studio",
|
||||||
"description": "Interactive GitHub Copilot app canvas extension for building Animated PNG (APNG) files from frames. Draw or upload frames, tune per-frame timing and compositing, preview live, send the result to your phone by QR, and export an animated .png.",
|
"description": "Interactive GitHub Copilot app canvas extension for building Animated PNG (APNG) files from frames. Draw or upload frames, tune per-frame timing and compositing, preview live, send the result to your phone by QR, and export an animated .png.",
|
||||||
"version": "1.0.1",
|
"version": "1.0.2",
|
||||||
"author": {
|
"author": {
|
||||||
"name": "Andrea Griffiths",
|
"name": "Andrea Griffiths",
|
||||||
"url": "https://github.com/AndreaGriffiths11"
|
"url": "https://github.com/AndreaGriffiths11"
|
||||||
|
|||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "arcade-canvas",
|
"name": "arcade-canvas",
|
||||||
"description": "Play five retro Phaser mini-games in a Copilot canvas while agents work.",
|
"description": "Play five retro Phaser mini-games in a Copilot canvas while agents work.",
|
||||||
"version": "1.0.1",
|
"version": "1.0.2",
|
||||||
"author": {
|
"author": {
|
||||||
"name": "Dan Wahlin",
|
"name": "Dan Wahlin",
|
||||||
"url": "https://github.com/DanWahlin"
|
"url": "https://github.com/DanWahlin"
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "backlog-swipe-triage",
|
"name": "backlog-swipe-triage",
|
||||||
"description": "Quickly swipe through backlog issues to triage decisions like assign, needs-info, defer, close, or ignore.",
|
"description": "Quickly swipe through backlog issues to triage decisions like assign, needs-info, defer, close, or ignore.",
|
||||||
"version": "1.0.1",
|
"version": "1.0.2",
|
||||||
"author": {
|
"author": {
|
||||||
"name": "James Montemagno",
|
"name": "James Montemagno",
|
||||||
"url": "https://github.com/jamesmontemagno"
|
"url": "https://github.com/jamesmontemagno"
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "chromium-control-canvas",
|
"name": "chromium-control-canvas",
|
||||||
"description": "Opens a real Chromium window you can navigate and interact with from a Copilot canvas control panel and agent actions.",
|
"description": "Opens a real Chromium window you can navigate and interact with from a Copilot canvas control panel and agent actions.",
|
||||||
"version": "1.0.1",
|
"version": "1.0.2",
|
||||||
"author": {
|
"author": {
|
||||||
"name": "Andrea Griffiths",
|
"name": "Andrea Griffiths",
|
||||||
"url": "https://github.com/AndreaGriffiths11"
|
"url": "https://github.com/AndreaGriffiths11"
|
||||||
|
|||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "color-orb",
|
"name": "color-orb",
|
||||||
"description": "A visual orb that users can ask the agent to recolor while showing a live activity log in the canvas.",
|
"description": "A visual orb that users can ask the agent to recolor while showing a live activity log in the canvas.",
|
||||||
"version": "1.0.1",
|
"version": "1.0.2",
|
||||||
"author": {
|
"author": {
|
||||||
"name": "Aaron Powell",
|
"name": "Aaron Powell",
|
||||||
"url": "https://github.com/aaronpowell"
|
"url": "https://github.com/aaronpowell"
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "connector-namespaces",
|
"name": "connector-namespaces",
|
||||||
"description": "Browse, connect, and open MCP connectors from an Azure Connector Namespace.",
|
"description": "Browse, connect, and open MCP connectors from an Azure Connector Namespace.",
|
||||||
"version": "1.1.1",
|
"version": "1.1.2",
|
||||||
"author": {
|
"author": {
|
||||||
"name": "Alex Yang",
|
"name": "Alex Yang",
|
||||||
"url": "https://github.com/alexyaang"
|
"url": "https://github.com/alexyaang"
|
||||||
|
|||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "diagram-viewer",
|
"name": "diagram-viewer",
|
||||||
"description": "Render diagrams, click nodes to drill down, and view agent-generated explanations directly in the canvas.",
|
"description": "Render diagrams, click nodes to drill down, and view agent-generated explanations directly in the canvas.",
|
||||||
"version": "1.0.1",
|
"version": "1.0.2",
|
||||||
"author": {
|
"author": {
|
||||||
"name": "Aaron Powell",
|
"name": "Aaron Powell",
|
||||||
"url": "https://github.com/aaronpowell"
|
"url": "https://github.com/aaronpowell"
|
||||||
|
|||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "feedback-themes",
|
"name": "feedback-themes",
|
||||||
"description": "Explore grouped customer feedback signals by impact and drill into a theme to guide product next steps.",
|
"description": "Explore grouped customer feedback signals by impact and drill into a theme to guide product next steps.",
|
||||||
"version": "1.0.1",
|
"version": "1.0.2",
|
||||||
"author": {
|
"author": {
|
||||||
"name": "Aaron Powell",
|
"name": "Aaron Powell",
|
||||||
"url": "https://github.com/aaronpowell"
|
"url": "https://github.com/aaronpowell"
|
||||||
|
|||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "gesture-review",
|
"name": "gesture-review",
|
||||||
"description": "Review pull requests with a live camera feed and approve or reject using thumbs-up/thumbs-down gestures.",
|
"description": "Review pull requests with a live camera feed and approve or reject using thumbs-up/thumbs-down gestures.",
|
||||||
"version": "1.0.1",
|
"version": "1.0.2",
|
||||||
"author": {
|
"author": {
|
||||||
"name": "Aaron Powell",
|
"name": "Aaron Powell",
|
||||||
"url": "https://github.com/aaronpowell"
|
"url": "https://github.com/aaronpowell"
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "java-modernization-studio",
|
"name": "java-modernization-studio",
|
||||||
"description": "Drive the GitHub Copilot App Modernization for Java workflow from an interactive canvas: environment readiness, repo assessment, prioritized plan and progress, validation gates, and one-click predefined-task runs grounded in the repo's real artifacts.",
|
"description": "Drive the GitHub Copilot App Modernization for Java workflow from an interactive canvas: environment readiness, repo assessment, prioritized plan and progress, validation gates, and one-click predefined-task runs grounded in the repo's real artifacts.",
|
||||||
"version": "1.0.1",
|
"version": "1.0.2",
|
||||||
"author": {
|
"author": {
|
||||||
"name": "Ayan Gupta",
|
"name": "Ayan Gupta",
|
||||||
"url": "https://github.com/ayangupt"
|
"url": "https://github.com/ayangupt"
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "release-notes-showcase",
|
"name": "release-notes-showcase",
|
||||||
"description": "Compose and refine launch-ready release notes with contributor callouts and export-friendly output.",
|
"description": "Compose and refine launch-ready release notes with contributor callouts and export-friendly output.",
|
||||||
"version": "1.0.1",
|
"version": "1.0.2",
|
||||||
"author": {
|
"author": {
|
||||||
"name": "Kayla Cinnamon",
|
"name": "Kayla Cinnamon",
|
||||||
"url": "https://github.com/cinnamon-msft"
|
"url": "https://github.com/cinnamon-msft"
|
||||||
|
|||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "repo-actions-hub",
|
"name": "repo-actions-hub",
|
||||||
"description": "Browse repository GitHub Actions workflows, inspect recent runs, and trigger manual workflow_dispatch runs from a Copilot canvas.",
|
"description": "Browse repository GitHub Actions workflows, inspect recent runs, and trigger manual workflow_dispatch runs from a Copilot canvas.",
|
||||||
"version": "1.0.1",
|
"version": "1.0.2",
|
||||||
"author": {
|
"author": {
|
||||||
"name": "James Montemagno",
|
"name": "James Montemagno",
|
||||||
"url": "https://github.com/jamesmontemagno"
|
"url": "https://github.com/jamesmontemagno"
|
||||||
|
|||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "site-studio",
|
"name": "site-studio",
|
||||||
"description": "Plan, draft, and track a personal website section by section — a shared canvas where you and your agent author content, watch progress, and review every change.",
|
"description": "Plan, draft, and track a personal website section by section — a shared canvas where you and your agent author content, watch progress, and review every change.",
|
||||||
"version": "1.0.1",
|
"version": "1.0.2",
|
||||||
"author": {
|
"author": {
|
||||||
"name": "Ayan Gupta",
|
"name": "Ayan Gupta",
|
||||||
"url": "https://github.com/ayangupt"
|
"url": "https://github.com/ayangupt"
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "tiny-tool-town-submitter",
|
"name": "tiny-tool-town-submitter",
|
||||||
"description": "Inspect a repository, improve Tiny Tool Town readiness, submit its listing issue, and launch remediation work.",
|
"description": "Inspect a repository, improve Tiny Tool Town readiness, submit its listing issue, and launch remediation work.",
|
||||||
"version": "1.0.1",
|
"version": "1.0.2",
|
||||||
"author": {
|
"author": {
|
||||||
"name": "James Montemagno",
|
"name": "James Montemagno",
|
||||||
"url": "https://github.com/jamesmontemagno"
|
"url": "https://github.com/jamesmontemagno"
|
||||||
|
|||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "token-pacman",
|
"name": "token-pacman",
|
||||||
"description": "Visualizes live session AI-credit usage as a Pac-Man board with pellets, ghosts, fruit milestones, and game-over limits.",
|
"description": "Visualizes live session AI-credit usage as a Pac-Man board with pellets, ghosts, fruit milestones, and game-over limits.",
|
||||||
"version": "1.0.1",
|
"version": "1.0.2",
|
||||||
"author": {
|
"author": {
|
||||||
"name": "James Montemagno",
|
"name": "James Montemagno",
|
||||||
"url": "https://github.com/jamesmontemagno"
|
"url": "https://github.com/jamesmontemagno"
|
||||||
|
|||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "where-was-i",
|
"name": "where-was-i",
|
||||||
"description": "Reconstruct your dev context (branch, commits, uncommitted work, PR clues) and trigger a resume prompt to continue quickly.",
|
"description": "Reconstruct your dev context (branch, commits, uncommitted work, PR clues) and trigger a resume prompt to continue quickly.",
|
||||||
"version": "1.0.1",
|
"version": "1.0.2",
|
||||||
"author": {
|
"author": {
|
||||||
"name": "Aaron Powell",
|
"name": "Aaron Powell",
|
||||||
"url": "https://github.com/aaronpowell"
|
"url": "https://github.com/aaronpowell"
|
||||||
|
|||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "work-hub",
|
"name": "work-hub",
|
||||||
"description": "Generic cross-repo command center canvas for GitHub Copilot with onboarding, focus planning, repo health, work signals, and session cleanup.",
|
"description": "Generic cross-repo command center canvas for GitHub Copilot with onboarding, focus planning, repo health, work signals, and session cleanup.",
|
||||||
"version": "1.0.1",
|
"version": "1.0.2",
|
||||||
"author": {
|
"author": {
|
||||||
"name": "James Montemagno",
|
"name": "James Montemagno",
|
||||||
"url": "https://github.com/jamesmontemagno"
|
"url": "https://github.com/jamesmontemagno"
|
||||||
|
|||||||
Reference in New Issue
Block a user