docs(plugins): consolidate guides and document npm publishing

This commit is contained in:
Mohamed Boudra
2026-09-17 09:17:39 +02:00
parent 8c4b7e5f79
commit 7e4f9ecf36
31 changed files with 561 additions and 1680 deletions
@@ -210,7 +210,7 @@ async function expectPluginSourceDocsOpen(page: Page): Promise<void> {
await page.getByRole("link", { name: "Docs", exact: true }).click();
const docsPage = await docsPagePromise;
try {
await docsPage.waitForURL("https://paseo.sh/docs/plugins/v0.8/reference#plugin-sources", {
await docsPage.waitForURL("https://paseo.sh/docs/plugins/reference#plugin-sources", {
waitUntil: "commit",
});
} finally {
@@ -71,7 +71,7 @@ it("rejects catalogs without requirements from pre-0.8 daemons", () => {
result.installCatalog("host", [{ id: "example", clientBundle }], { client });
expect(starts()).toBe(0);
expect(result.getEvaluationError("host", "example")).toContain(
"https://paseo.sh/docs/plugins/v0.8/migration",
"https://paseo.sh/docs/plugins/migration",
);
});
@@ -33,7 +33,7 @@ import { settingsStyles } from "@/styles/settings";
import { confirmDialog } from "@/utils/confirm-dialog";
const pluginQueryKey = (serverId: string) => ["plugins", serverId] as const;
const PLUGIN_SOURCE_DOCS_URL = "https://paseo.sh/docs/plugins/v0.8/reference#plugin-sources";
const PLUGIN_SOURCE_DOCS_URL = "https://paseo.sh/docs/plugins/reference#plugin-sources";
type PluginRowAction = "reload" | "enable" | "disable" | "remove";
function errorMessage(error: unknown): string {
+1 -1
View File
@@ -9,7 +9,7 @@ describe("plugin runtime-entry migration guide", () => {
if (!context) throw new Error("PluginClientContext was not found");
const methods = [...context.matchAll(/^\s+(add[A-Z]\w*)\(/gm)].map((match) => match[1]);
const migration = await readFile(
fileURLToPath(new URL("../../../public-docs/plugins/v0.8/migration.md", import.meta.url)),
fileURLToPath(new URL("../../../public-docs/plugins/migration.md", import.meta.url)),
"utf8",
);
const table = migration.slice(
+1 -1
View File
@@ -33,7 +33,7 @@ export function assertPluginCompatibility(input: PluginCompatibilityInput): void
if (satisfies(version, range) || satisfies(stableCore, range)) return;
const action =
input.requirements?.paseo === undefined
? "This plugin has no requirements.paseo and targets Paseo before 0.8. Ask its author to migrate it: https://paseo.sh/docs/plugins/v0.8/migration"
? "This plugin has no requirements.paseo and targets Paseo before 0.8. Ask its author to migrate it: https://paseo.sh/docs/plugins/migration"
: `Use a compatible plugin version or update the ${input.runtime}.`;
throw new Error(
`Plugin "${input.id}" requires Paseo ${range}. Your ${input.runtime} is ${input.version}. ${action}`,
@@ -1015,7 +1015,7 @@ export default function contribute(plugin: unknown) {
"Plugin entry points are missing",
);
await expect(service.installDirectory({ path: legacy })).rejects.toThrow(
"This plugin was made for an older version of Paseo and cannot run on Paseo v0.8. Ask its author to update it. Plugin authors can follow the migration guide: https://paseo.sh/docs/plugins/v0.8/migration",
"This plugin was made for an older version of Paseo and cannot run on Paseo v0.8. Ask its author to update it. Plugin authors can follow the migration guide: https://paseo.sh/docs/plugins/migration",
);
await expect(service.installDirectory({ path: startupFailure })).rejects.toThrow(
"startup exploded",
@@ -1025,7 +1025,7 @@ export default function contribute(plugin: unknown) {
id: "legacy-plugin",
status: "failed",
error:
"This plugin was made for an older version of Paseo and cannot run on Paseo v0.8. Ask its author to update it. Plugin authors can follow the migration guide: https://paseo.sh/docs/plugins/v0.8/migration",
"This plugin was made for an older version of Paseo and cannot run on Paseo v0.8. Ask its author to update it. Plugin authors can follow the migration guide: https://paseo.sh/docs/plugins/migration",
}),
expect.objectContaining({
id: "missing-entry",
@@ -80,7 +80,7 @@ it("marks pre-0.8 plugins failed on startup and recovers after migration and rel
expect(await service.listPlugins()).toEqual([
expect.objectContaining({
status: "failed",
error: expect.stringContaining("https://paseo.sh/docs/plugins/v0.8/migration"),
error: expect.stringContaining("https://paseo.sh/docs/plugins/migration"),
}),
]);
await writePlugin(root, ">=0.8.0");
@@ -97,7 +97,7 @@ it("still requires entry migration when an old plugin adds a compatible requirem
await writeFile(path.join(root, "index.ts"), "export default () => () => {};");
const { service } = await host();
await expect(service.installDirectory({ path: root })).rejects.toThrow(
"https://paseo.sh/docs/plugins/v0.8/migration",
"https://paseo.sh/docs/plugins/migration",
);
});
@@ -1422,7 +1422,7 @@ export default function contribute(server: { registerProvider(provider: Provider
const runtime = createTestRuntime();
await expect(runtime.startPlugin("legacy", directory)).rejects.toThrow(
"This plugin was made for an older version of Paseo and cannot run on Paseo v0.8. Ask its author to update it. Plugin authors can follow the migration guide: https://paseo.sh/docs/plugins/v0.8/migration",
"This plugin was made for an older version of Paseo and cannot run on Paseo v0.8. Ask its author to update it. Plugin authors can follow the migration guide: https://paseo.sh/docs/plugins/migration",
);
});
@@ -276,7 +276,7 @@ async function resolveEntryPaths(directory: string): Promise<{
const legacyEntry = await findEntry(directory, ["index.ts", "index.tsx"]);
if (legacyEntry) {
throw new Error(
"This plugin was made for an older version of Paseo and cannot run on Paseo v0.8. Ask its author to update it. Plugin authors can follow the migration guide: https://paseo.sh/docs/plugins/v0.8/migration",
"This plugin was made for an older version of Paseo and cannot run on Paseo v0.8. Ask its author to update it. Plugin authors can follow the migration guide: https://paseo.sh/docs/plugins/migration",
);
}
throw new Error(
+8 -8
View File
@@ -157,15 +157,15 @@ export function getDoc(slug: string): Doc | undefined {
return getDocs().find((d) => d.slug === slug);
}
const legacyPluginDocRedirects: Readonly<Record<string, string>> = {
"/docs/plugins/reference": "/docs/plugins/v0.7/reference",
"/docs/plugins/reference.md": "/docs/plugins/v0.7/reference.md",
"/docs/plugins/migration": "/docs/plugins/v0.8/migration",
"/docs/plugins/migration.md": "/docs/plugins/v0.8/migration.md",
};
export function getLegacyDocsRedirect(pathname: string): string | undefined {
return legacyPluginDocRedirects[pathname];
const match =
/^\/docs\/plugins\/v0\.[78](?:(\.md)|\/(index|reference|providers|migration)(\.md)?)?\/?$/.exec(
pathname,
);
if (!match) return undefined;
const page = match[2] && match[2] !== "index" ? `/${match[2]}` : "";
const extension = match[1] || match[3] ? ".md" : "";
return `/docs/plugins${page}${extension}`;
}
function formatLabel(segment: string): string {
@@ -1,8 +1,8 @@
import { describe, expect, it } from "vitest";
import { buildDocsNavTree, getDocs, getLegacyDocsRedirect } from "./docs";
import { buildDocsNavTree, getDoc, getDocs, getLegacyDocsRedirect } from "./docs";
describe("versioned plugin documentation", () => {
it("keeps stable and beta releases together in the Plugins navigation", () => {
describe("plugin documentation", () => {
it("groups current guides by task without version navigation", () => {
const plugins = buildDocsNavTree(getDocs()).find(
(node) => node.type === "category" && node.label === "Plugins",
);
@@ -10,35 +10,32 @@ describe("versioned plugin documentation", () => {
expect(plugins).toMatchObject({
type: "category",
children: [
{ type: "page", label: "Versions", href: "/docs/plugins" },
{
type: "group",
label: "Paseo v0.7 — Current",
href: "/docs/plugins/v0.7",
children: [{ type: "page", label: "Reference", href: "/docs/plugins/v0.7/reference" }],
},
{
type: "group",
label: "Paseo v0.8 — Beta",
href: "/docs/plugins/v0.8",
children: [
{ type: "page", label: "Provider plugins", href: "/docs/plugins/v0.8/providers" },
{ type: "page", label: "Migration", href: "/docs/plugins/v0.8/migration" },
{ type: "page", label: "Reference", href: "/docs/plugins/v0.8/reference" },
],
},
{ type: "page", label: "Quickstart", href: "/docs/plugins" },
{ type: "page", label: "Publishing", href: "/docs/plugins/publishing" },
{ type: "page", label: "Provider plugins", href: "/docs/plugins/providers" },
{ type: "page", label: "Migration", href: "/docs/plugins/migration" },
{ type: "page", label: "Reference", href: "/docs/plugins/reference" },
],
});
});
it("keeps existing plugin documentation URLs working", () => {
expect(getLegacyDocsRedirect("/docs/plugins/reference")).toBe("/docs/plugins/v0.7/reference");
expect(getLegacyDocsRedirect("/docs/plugins/reference.md")).toBe(
"/docs/plugins/v0.7/reference.md",
);
expect(getLegacyDocsRedirect("/docs/plugins/migration")).toBe("/docs/plugins/v0.8/migration");
expect(getLegacyDocsRedirect("/docs/plugins/migration.md")).toBe(
"/docs/plugins/v0.8/migration.md",
);
it.each(["v0.7", "v0.8"])("redirects %s links to existing current pages", (version) => {
for (const page of ["", "/index", "/reference", "/providers", "/migration"]) {
const target = `/docs/plugins${page === "/index" ? "" : page}`;
expect(getDoc(target.slice("/docs/".length))).toBeDefined();
for (const extension of ["", ".md"]) {
const source = `/docs/plugins/${version}${page}${extension}`;
expect(getLegacyDocsRedirect(source)).toBe(`${target}${extension}`);
expect(getLegacyDocsRedirect(`${source}/`)).toBe(`${target}${extension}`);
}
}
});
it("serves canonical pages without redirect loops", () => {
for (const page of ["", "/publishing", "/reference", "/providers", "/migration"]) {
expect(getLegacyDocsRedirect(`/docs/plugins${page}`)).toBeUndefined();
expect(getLegacyDocsRedirect(`/docs/plugins${page}.md`)).toBeUndefined();
}
expect(getLegacyDocsRedirect("/docs/plugins/v0.8/unknown")).toBeUndefined();
});
});