Files
paseo/packages/protocol/src/plugin-source-reference.ts
T
Mohamed Boudra b5e9747e2a Add the reviewed plugin registry and the unlinked plugin directory pages (#5986)
* feat(plugins): install reviewed artifacts from open registries

* fix(website): build protocol before deployment checks

* fix(website): make plugin pages discoverable to search engines

Resolve missing registry entries in route loaders to return HTTP 404. Serve a registry-backed sitemap so newly listed plugins and authors are discoverable without rebuilding the website.

* feat(website): keep plugin directory unlinked for coordinated launch

Preserve direct routes and SEO while retaining the partner directory link. Cover nested Git artifacts through registry installation and pinned website commands.

* fix(website): link plugin cards by registry owner and stub the clipboard in e2e

Plugin and author URLs come from the owner segment of the plugin ID, not the author's GitHub login, so a listed plugin always resolves. The Playwright spec records clipboard writes instead of granting a permission WebKit does not have.
2026-10-03 16:30:13 +01:00

73 lines
3.1 KiB
TypeScript

import { parsePluginRegistryReference } from "./plugin-registry.js";
import type { PluginInstallation } from "./messages.js";
export interface PluginSourceReference {
source: string;
pluginPath: string | undefined;
}
export function parsePluginSourceReference(reference: string): PluginSourceReference {
if (parsePluginRegistryReference(reference)) return { source: reference, pluginPath: undefined };
const prefix = /^(npm:|github:|git:(?!\/\/))/.exec(reference)?.[0];
const source = prefix ? reference.slice(prefix.length) : reference;
const parsed = splitPluginPath(source, { scp: prefix !== "npm:" });
return { ...parsed, source: `${prefix ?? ""}${parsed.source}` };
}
function splitPluginPath(reference: string, options: { scp: boolean }): PluginSourceReference {
const separator = reference.lastIndexOf(":");
if (separator === -1) return { source: reference, pluginPath: undefined };
const pluginPath = reference.slice(separator + 1);
if (!isPortableRelativePluginPath(pluginPath)) {
return { source: reference, pluginPath: undefined };
}
const scheme = reference.indexOf("://");
if (scheme !== -1) {
const pathStart = reference.indexOf("/", scheme + 3);
if (pathStart === -1 || separator < pathStart) {
return { source: reference, pluginPath: undefined };
}
} else if (options.scp) {
const scpSeparator = reference.match(/^[^/@\s]+@[^:\s]+:/)?.[0].length;
if (scpSeparator !== undefined && separator === scpSeparator - 1) {
return { source: reference, pluginPath: undefined };
}
}
return { source: reference.slice(0, separator), pluginPath };
}
export function formatPluginSourceReference(
source: string,
pluginPath: string | undefined,
): string {
if (pluginPath === undefined) return source;
if (!isPortableRelativePluginPath(pluginPath)) {
throw new Error("Plugin path must be a portable path relative to the repository");
}
return `${source}:${pluginPath}`;
}
function isPortableRelativePluginPath(pluginPath: string): boolean {
if (pluginPath === ".") return true;
if (!pluginPath || pluginPath.startsWith("/") || pluginPath.startsWith("\\")) return false;
if (/^[A-Za-z]:/.test(pluginPath) || pluginPath.includes(":")) return false;
return pluginPath.split(/[\\/]/).every((part) => part !== "" && part !== "." && part !== "..");
}
export function formatPluginIdentity(identity: PluginInstallation["identity"]): string {
if (identity.kind === "directory") return identity.path;
if (identity.registry) return `${new URL(identity.registry.url).host}/${identity.registry.id}`;
const source = identity.kind === "npm" ? `npm:${identity.packageName}` : `git:${identity.remote}`;
return identity.pluginPath === "." ? source : `${source}:${identity.pluginPath}`;
}
export function formatPluginInstallation(installation: PluginInstallation): string {
const identity = formatPluginIdentity(installation.identity);
const revision =
installation.identity.kind === "git"
? installation.currentRevision?.slice(0, 12)
: installation.currentRevision;
return revision ? `${identity} · ${revision}` : identity;
}