chore: publish from main

This commit is contained in:
github-actions[bot]
2026-06-25 04:53:20 +00:00
parent ae6d0ca79f
commit d1cb70036a
15 changed files with 139 additions and 1 deletions
+26
View File
@@ -101,6 +101,25 @@ function normalizeText(value, fallback = "") {
return typeof value === "string" ? value.trim() : fallback;
}
/**
* Normalize an author value (npm string form or { name, url } object) to
* { name, url? } | null. Returns null when no usable name is present.
*/
function normalizeAuthor(value) {
if (!value) return null;
if (typeof value === "string") {
const name = value.trim();
return name ? { name } : null;
}
if (typeof value === "object") {
const name = normalizeText(value.name);
if (!name) return null;
const url = normalizeText(value.url);
return url ? { name, url } : { name };
}
return null;
}
/**
* Find the latest git-modified date for any file under a directory.
*/
@@ -1002,6 +1021,10 @@ function generateCanvasManifest(gitDates, commitSha) {
const packageJson = fs.existsSync(packageJsonPath)
? JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"))
: {};
const canvasJsonPath = path.join(extensionDir, "canvas.json");
const canvasJson = fs.existsSync(canvasJsonPath)
? JSON.parse(fs.readFileSync(canvasJsonPath, "utf-8"))
: {};
const keywords = Array.isArray(packageJson.keywords)
? [...new Set(packageJson.keywords.filter((keyword) => typeof keyword === "string").map((keyword) => keyword.trim()).filter(Boolean))].sort((a, b) => a.localeCompare(b))
: [];
@@ -1044,6 +1067,7 @@ function generateCanvasManifest(gitDates, commitSha) {
installUrl,
sourceUrl: null,
external: false,
author: normalizeAuthor(canvasJson.author),
keywords,
});
}
@@ -1116,6 +1140,7 @@ function generateCanvasManifest(gitDates, commitSha) {
installUrl,
sourceUrl: sourceUrl || null,
external: true,
author: normalizeAuthor(ext?.author),
keywords,
});
}
@@ -1199,6 +1224,7 @@ function writePerExtensionCanvasManifests(canvasManifestData) {
name: item.name,
description: item.description || "Canvas extension",
version: item.version || "1.0.0",
...(item.author ? { author: item.author } : {}),
keywords: Array.isArray(item.keywords)
? [...new Set(item.keywords)].sort((a, b) => a.localeCompare(b))
: [],
@@ -3,6 +3,10 @@
"name": "Accessibility Kanban",
"description": "Kanban board to manage accessibility issues, allow you to plan, track, and complete remediation work.",
"version": "1.0.0",
"author": {
"name": "Aaron Powell",
"url": "https://github.com/aaronpowell"
},
"keywords": [
"accessibility",
"github-issues",
+4
View File
@@ -3,6 +3,10 @@
"name": "Agent Arcade",
"description": "Play five retro Phaser mini-games in a Copilot canvas while agents work.",
"version": "1.0.0",
"author": {
"name": "Dan Wahlin",
"url": "https://github.com/DanWahlin"
},
"keywords": [
"arcade-games",
"copilot-canvas",
@@ -3,6 +3,10 @@
"name": "Backlog Swipe Triage",
"description": "Quickly swipe through backlog issues to triage decisions like assign, needs-info, defer, close, or ignore.",
"version": "1.0.0",
"author": {
"name": "James Montemagno",
"url": "https://github.com/jamesmontemagno"
},
"keywords": [
"agent-assignment",
"backlog-triage",
@@ -3,6 +3,10 @@
"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.",
"version": "1.0.0",
"author": {
"name": "Andrea Griffiths",
"url": "https://github.com/AndreaGriffiths11"
},
"keywords": [
"browser-control",
"chromium-browser",
+4
View File
@@ -3,6 +3,10 @@
"name": "Color Orb",
"description": "A visual orb that users can ask the agent to recolor while showing a live activity log in the canvas.",
"version": "1.0.0",
"author": {
"name": "Aaron Powell",
"url": "https://github.com/aaronpowell"
},
"keywords": [
"agent-actions",
"color-picker",
+4
View File
@@ -3,6 +3,10 @@
"name": "Diagram Explorer",
"description": "Render diagrams, click nodes to drill down, and view agent-generated explanations directly in the canvas.",
"version": "1.0.0",
"author": {
"name": "Aaron Powell",
"url": "https://github.com/aaronpowell"
},
"keywords": [
"architecture-mapping",
"canvas-navigation",
+1
View File
@@ -3,6 +3,7 @@
"id": "coffilot",
"name": "Coffilot",
"description": "Java-focused Copilot canvas extension from jdubois.",
"author": { "name": "Julien Dubois", "url": "https://github.com/jdubois" },
"keywords": [
"java",
"canvas",
+4
View File
@@ -3,6 +3,10 @@
"name": "Feedback Themes",
"description": "Explore grouped customer feedback signals by impact and drill into a theme to guide product next steps.",
"version": "1.0.0",
"author": {
"name": "Aaron Powell",
"url": "https://github.com/aaronpowell"
},
"keywords": [
"customer-feedback",
"impact-prioritization",
+4
View File
@@ -3,6 +3,10 @@
"name": "Gesture PR Review",
"description": "Review pull requests with a live camera feed and approve or reject using thumbs-up/thumbs-down gestures.",
"version": "1.0.0",
"author": {
"name": "Aaron Powell",
"url": "https://github.com/aaronpowell"
},
"keywords": [
"camera-input",
"gesture-control",
@@ -3,6 +3,10 @@
"name": "Release Notes Showcase",
"description": "Compose and refine launch-ready release notes with contributor callouts and export-friendly output.",
"version": "1.0.0",
"author": {
"name": "James Montemagno",
"url": "https://github.com/jamesmontemagno"
},
"keywords": [
"changelog",
"contributor-callouts",
+4
View File
@@ -3,6 +3,10 @@
"name": "Where Was I?",
"description": "Reconstruct your dev context (branch, commits, uncommitted work, PR clues) and trigger a resume prompt to continue quickly.",
"version": "1.0.0",
"author": {
"name": "Aaron Powell",
"url": "https://github.com/aaronpowell"
},
"keywords": [
"branch-state",
"developer-context",
+27 -1
View File
@@ -1,4 +1,10 @@
import { escapeHtml, getGitHubUrl, getLastUpdatedHtml } from "../utils";
import {
escapeHtml,
getGitHubHandle,
getGitHubUrl,
getLastUpdatedHtml,
sanitizeUrl,
} from "../utils";
import { renderEmptyStateHtml, renderSharedCardHtml } from "./card-render";
export interface RenderableExtension {
@@ -34,6 +40,7 @@ export interface RenderableExtension {
installUrl?: string | null;
sourceUrl?: string | null;
external?: boolean;
author?: { name: string; url?: string } | null;
}
export type ExtensionSortOption = "title" | "lastUpdated";
@@ -92,8 +99,27 @@ export function renderExtensionsHtml(items: RenderableExtension[]): string {
</div>
`;
const authorName = item.author?.name;
const authorUrl = item.author?.url;
const authorHandle =
authorName && authorUrl
? getGitHubHandle(authorUrl, authorName)
: authorName || "";
const authorHtml = authorName
? `<span class="resource-tag resource-author">by ${
authorUrl
? `<a href="${escapeHtml(
sanitizeUrl(authorUrl)
)}" target="_blank" rel="noopener noreferrer" title="${escapeHtml(
authorName
)}">${escapeHtml(authorHandle)}</a>`
: escapeHtml(authorName)
}</span>`
: "";
const metaHtml = `
${item.external ? '<span class="resource-tag">External</span>' : ""}
${authorHtml}
${getLastUpdatedHtml(item.lastUpdated)}
`;
+20
View File
@@ -12,9 +12,11 @@ import {
copyToClipboard,
fetchData,
formatRelativeTime,
getGitHubHandle,
getGitHubUrl,
getQueryParam,
getQueryParamValues,
sanitizeUrl,
showToast,
updateQueryParams,
} from "../utils";
@@ -178,6 +180,24 @@ function openDetailsModal(
if (item.external) {
metaParts.push('<span class="resource-tag">External</span>');
}
if (item.author?.name) {
const authorName = item.author.name;
const authorUrl = item.author.url;
const authorHandle = authorUrl
? getGitHubHandle(authorUrl, authorName)
: authorName;
metaParts.push(
authorUrl
? `<span class="resource-author">by <a href="${escapeHtml(
sanitizeUrl(authorUrl)
)}" target="_blank" rel="noopener noreferrer" title="${escapeHtml(
authorName
)}">${escapeHtml(authorHandle)}</a></span>`
: `<span class="resource-author">by ${escapeHtml(
authorName
)}</span>`
);
}
if (item.lastUpdated) {
metaParts.push(
`<span class="last-updated">Updated ${escapeHtml(
+25
View File
@@ -389,6 +389,31 @@ export function sanitizeUrl(url: string | null | undefined): string {
return "#";
}
/**
* Derive a GitHub @handle from a profile URL
* (e.g. "https://github.com/aaronpowell" -> "@aaronpowell").
* Falls back to the provided value when the URL is not a github.com profile.
*/
export function getGitHubHandle(
url: string | null | undefined,
fallback = ""
): string {
if (!url) return fallback;
try {
const parsed = new URL(url);
const host = parsed.hostname.replace(/^www\./, "");
if (host === "github.com") {
const segments = parsed.pathname.split("/").filter(Boolean);
if (segments.length === 1) {
return `@${segments[0]}`;
}
}
} catch {
// Invalid URL
}
return fallback;
}
/**
* Truncate text with ellipsis
*/