diff --git a/website/README.md b/website/README.md index f5193e2..1c6d9f1 100644 --- a/website/README.md +++ b/website/README.md @@ -25,6 +25,10 @@ https://raw.githubusercontent.com/vastsa/pi-desktop-plugins/main/catalog.json Catalog responses are revalidated every five minutes. Set `CATALOG_URL` to use a different catalog provider during preview or development. +Package downloads use the catalog's `artifactBaseUrl` when it is present, and +otherwise resolve relative to `CATALOG_URL`. Run `pnpm test` with Node.js 22.6 or +later to check URL resolution. + The site defaults to English and uses `?lang=` for language selection. When no explicit language is present, it matches the browser's `Accept-Language` header against the locales available in the catalog. A manual diff --git a/website/app/plugins/[id]/page.tsx b/website/app/plugins/[id]/page.tsx index c29d877..b0dc44f 100644 --- a/website/app/plugins/[id]/page.tsx +++ b/website/app/plugins/[id]/page.tsx @@ -41,7 +41,7 @@ export default async function PluginDetailPage({ params, searchParams }: { param const version = currentVersion(plugin); const permissions = version.permissions ?? []; const risk = permissionRisk(permissions); - const packageHref = packageUrl(plugin); + const packageHref = packageUrl(plugin, catalog); return <>
{copy.detail.plugins}/{localized.name}

{localized.name}

{localized.description}

{copy.detail.by} {plugin.author}ยทv{version.version}{plugin.verified && {copy.detail.catalogOfficial}}

{copy.detail.about}

{localized.readmeMarkdown ? {localized.readmeMarkdown} :

{copy.detail.noReadme}

}
; } diff --git a/website/lib/catalog.ts b/website/lib/catalog.ts index 95787ab..cb6fd34 100644 --- a/website/lib/catalog.ts +++ b/website/lib/catalog.ts @@ -1,4 +1,5 @@ import { defaultLocale, siteLocales, type Locale } from "./i18n"; +import { resolvePackageUrl } from "./package-url"; export type Permission = string; @@ -100,10 +101,8 @@ export function currentVersion(plugin: Plugin): PluginVersion { return plugin.versions[0]; } -export function packageUrl(plugin: Plugin): string { - const url = currentVersion(plugin).url; - if (url.startsWith("http://") || url.startsWith("https://")) return url; - return new URL(url, CATALOG_URL).toString(); +export function packageUrl(plugin: Plugin, catalog: Pick): string { + return resolvePackageUrl(currentVersion(plugin).url, CATALOG_URL, catalog.artifactBaseUrl); } export function formatBytes(bytes?: number): string { diff --git a/website/lib/package-url.ts b/website/lib/package-url.ts new file mode 100644 index 0000000..6724e85 --- /dev/null +++ b/website/lib/package-url.ts @@ -0,0 +1,6 @@ +export function resolvePackageUrl(url: string, catalogUrl: string, artifactBaseUrl?: string): string { + if (url.startsWith("http://") || url.startsWith("https://")) return url; + const base = artifactBaseUrl?.trim(); + if (!base) return new URL(url, catalogUrl).toString(); + return new URL(url.replace(/^\/+/, ""), base.endsWith("/") ? base : `${base}/`).toString(); +} diff --git a/website/package.json b/website/package.json index e53acfa..5fd0d46 100644 --- a/website/package.json +++ b/website/package.json @@ -5,6 +5,7 @@ "scripts": { "dev": "next dev", "build": "next build", + "test": "node --experimental-strip-types --test tests/*.test.mjs", "start": "next start", "lint": "next lint" }, diff --git a/website/tests/package-url.test.mjs b/website/tests/package-url.test.mjs new file mode 100644 index 0000000..5dd3f46 --- /dev/null +++ b/website/tests/package-url.test.mjs @@ -0,0 +1,32 @@ +import assert from "node:assert/strict"; +import test from "node:test"; +import { resolvePackageUrl } from "../lib/package-url.ts"; + +const catalogUrl = "https://plugins.aiuo.net/catalog.json"; +const mirrorBase = "https://raw.githubusercontent.com/vastsa/pi-desktop-plugins/main"; +const packagePath = "packages/pi.gitlens-0.2.7.piplug"; + +test("relative downloads use the declared artifact base on another host", () => { + for (const base of [mirrorBase, `${mirrorBase}/`]) { + assert.equal(resolvePackageUrl(packagePath, catalogUrl, base), `${mirrorBase}/${packagePath}`); + } +}); + +test("a leading slash in a package path still uses the artifact directory", () => { + assert.equal(resolvePackageUrl(`/${packagePath}`, catalogUrl, mirrorBase), `${mirrorBase}/${packagePath}`); +}); + +test("absolute package URLs keep their own origin and query string", () => { + for (const url of ["https://cdn.example/plugin.piplug?version=1", "http://localhost/plugin.piplug"]) { + assert.equal(resolvePackageUrl(url, catalogUrl, mirrorBase), url); + } +}); + +test("catalogs without an artifact base resolve relative to the catalog", () => { + for (const base of [undefined, "", " "]) { + assert.equal( + resolvePackageUrl(packagePath, "https://mirror.example/catalogs/catalog.json", base), + `https://mirror.example/catalogs/${packagePath}`, + ); + } +});