add sourceUrl to support packages hosted external to the package manager repo

This commit is contained in:
Smartsheet-JB-Brown 2025-04-17 12:59:13 -07:00
parent 876742a887
commit 1853ebaa9c
7 changed files with 138 additions and 19 deletions

View file

@ -0,0 +1,8 @@
name: Test Source URL
description: A test package with source URL
type: package
version: 1.0.0
sourceUrl: https://example.com/test-package
tags:
- test
- source-url

View file

@ -249,6 +249,7 @@ export class MetadataScanner {
items: [], // Initialize empty items array for all components
author: metadata.author,
authorUrl: metadata.authorUrl,
sourceUrl: metadata.sourceUrl,
}
}

View file

@ -44,7 +44,7 @@ describe("MetadataScanner", () => {
})
describe("Basic Metadata Scanning", () => {
it("should discover components with English metadata", async () => {
it("should discover components with English metadata and sourceUrl", async () => {
// Mock directory structure
const mockDirents = [
{
@ -86,6 +86,7 @@ name: Test Component
description: A test component
type: mcp server
version: 1.0.0
sourceUrl: https://example.com/component1
`),
)
@ -96,6 +97,54 @@ version: 1.0.0
expect(items[0].type).toBe("mcp server")
expect(items[0].url).toBe("https://example.com/repo/tree/main/component1")
expect(items[0].path).toBe("component1")
expect(items[0].sourceUrl).toBe("https://example.com/component1")
})
it("should handle missing sourceUrl in metadata", async () => {
const mockDirents = [
{
name: "component2",
isDirectory: () => true,
isFile: () => false,
},
{
name: "metadata.en.yml",
isDirectory: () => false,
isFile: () => true,
},
] as Dirent[]
const mockEmptyDirents = [] as Dirent[]
const mockStats = {
isDirectory: () => true,
isFile: () => true,
mtime: new Date(),
} as Stats
const mockedFs = jest.mocked(fs)
mockedFs.stat.mockResolvedValue(mockStats)
;(mockedFs.readdir as any).mockImplementation(async (path: any, options?: any) => {
if (path.toString().includes("/component2/")) {
return options?.withFileTypes ? mockEmptyDirents : []
}
return options?.withFileTypes ? mockDirents : mockDirents.map((d) => d.name)
})
mockedFs.readFile.mockResolvedValue(
Buffer.from(`
name: Test Component 2
description: A test component without sourceUrl
type: mcp server
version: 1.0.0
`),
)
const items = await metadataScanner.scanDirectory(mockBasePath, mockRepoUrl)
expect(items).toHaveLength(1)
expect(items[0].name).toBe("Test Component 2")
expect(items[0].type).toBe("mcp server")
expect(items[0].url).toBe("https://example.com/repo/tree/main/component2")
expect(items[0].path).toBe("component2")
expect(items[0].sourceUrl).toBeUndefined()
})
})
})

View file

@ -11,6 +11,7 @@ export const baseMetadataSchema = z.object({
tags: z.array(z.string()).optional(),
author: z.string().optional(),
authorUrl: z.string().url("Author URL must be a valid URL").optional(),
sourceUrl: z.string().url("Source URL must be a valid URL").optional(),
})
/**

View file

@ -27,6 +27,7 @@ export interface BaseMetadata {
tags?: string[]
author?: string
authorUrl?: string
sourceUrl?: string
}
/**

View file

@ -64,14 +64,19 @@ export const PackageManagerItemCard: React.FC<PackageManagerItemCardProps> = ({
}
const handleOpenUrl = () => {
let urlToOpen = item.sourceUrl && isValidUrl(item.sourceUrl) ? item.sourceUrl : item.repoUrl
// If sourceUrl is present and valid, use it directly without modifications
if (item.sourceUrl && isValidUrl(item.sourceUrl)) {
return vscode.postMessage({
type: "openExternal",
url: item.sourceUrl,
})
}
// If we have a defaultBranch, append it to the URL
// Otherwise use repoUrl with git path information
let urlToOpen = item.repoUrl
if (item.defaultBranch) {
urlToOpen = `${urlToOpen}/tree/${item.defaultBranch}`
// If we also have a path, append it
if (item.path) {
// Ensure path uses forward slashes and doesn't start with one
const normalizedPath = item.path.replace(/\\/g, "/").replace(/^\/+/, "")
urlToOpen = `${urlToOpen}/${normalizedPath}`
}
@ -192,11 +197,17 @@ export const PackageManagerItemCard: React.FC<PackageManagerItemCardProps> = ({
)}
</div>
<Button onClick={handleOpenUrl}>
<span className="codicon codicon-link-external mr-2"></span>
{item.sourceUrl
? t("package_manager:item_card.view")
: item.sourceName || t("package_manager:item_card.source")}
<Button
onClick={handleOpenUrl}
aria-label={
item.sourceUrl && isValidUrl(item.sourceUrl)
? ""
: item.sourceName || t("package_manager:item_card.source")
}>
<span
className={`codicon codicon-link-external${!item.sourceUrl || !isValidUrl(item.sourceUrl) ? " mr-2" : ""}`}></span>
{(!item.sourceUrl || !isValidUrl(item.sourceUrl)) &&
(item.sourceName || t("package_manager:item_card.source"))}
</Button>
</div>

View file

@ -95,18 +95,66 @@ describe("PackageManagerItemCard", () => {
expect(screen.getByText(/Apr \d{1,2}, 2025/)).toBeInTheDocument()
})
it("should handle source URL click", () => {
renderWithProviders(<PackageManagerItemCard {...defaultProps} />)
describe("URL handling", () => {
it("should use sourceUrl directly when present and valid", () => {
const itemWithSourceUrl = {
...mockItem,
sourceUrl: "https://example.com/direct-link",
defaultBranch: "main",
path: "some/path",
}
renderWithProviders(<PackageManagerItemCard {...defaultProps} item={itemWithSourceUrl} />)
// Find the source button by its text content
const sourceButton = screen.getByRole("button", {
name: /Source/i,
const button = screen.getByRole("button", { name: /^$/ }) // Button with no text, only icon
fireEvent.click(button)
expect(mockPostMessage).toHaveBeenCalledWith({
type: "openExternal",
url: "https://example.com/direct-link",
})
})
fireEvent.click(sourceButton)
expect(mockPostMessage).toHaveBeenCalledWith({
type: "openExternal",
url: "test-url",
it("should use repoUrl with git path when sourceUrl is not present", () => {
const itemWithGitPath = {
...mockItem,
defaultBranch: "main",
path: "some/path",
}
renderWithProviders(<PackageManagerItemCard {...defaultProps} item={itemWithGitPath} />)
const button = screen.getByRole("button", { name: /Source/i })
fireEvent.click(button)
expect(mockPostMessage).toHaveBeenCalledWith({
type: "openExternal",
url: "test-url/tree/main/some/path",
})
})
it("should show only icon when sourceUrl is present and valid", () => {
const itemWithSourceUrl = {
...mockItem,
sourceUrl: "https://example.com/direct-link",
}
renderWithProviders(<PackageManagerItemCard {...defaultProps} item={itemWithSourceUrl} />)
// Find the source button by its empty aria-label
const button = screen.getByRole("button", {
name: "", // Empty aria-label when sourceUrl is present
})
expect(button.querySelector(".codicon-link-external")).toBeInTheDocument()
expect(button.textContent).toBe("") // Verify no text content
})
it("should show text label when sourceUrl is not present", () => {
renderWithProviders(<PackageManagerItemCard {...defaultProps} />)
// Find the source button by its aria-label
const button = screen.getByRole("button", {
name: "Source",
})
expect(button.querySelector(".codicon-link-external")).toBeInTheDocument()
expect(button).toHaveTextContent(/Source/i)
})
})