From fd36066fdf5b9e4b07f04c1358b2829b583bd5f4 Mon Sep 17 00:00:00 2001 From: saberzero1 Date: Fri, 13 Feb 2026 19:34:56 +0100 Subject: [PATCH] feat: add PageTypePluginEntry for cross-boundary type compatibility Introduce PageTypePluginEntry with never[] parameter types to accept both internal and community PageType plugins in config arrays without casts, working around branded FullSlug contravariance mismatch. --- quartz/plugins/config.ts | 10 +++---- quartz/plugins/types.ts | 58 ++++++++++++++++------------------------ 2 files changed, 28 insertions(+), 40 deletions(-) diff --git a/quartz/plugins/config.ts b/quartz/plugins/config.ts index d19a738cd..710cdbea7 100644 --- a/quartz/plugins/config.ts +++ b/quartz/plugins/config.ts @@ -2,7 +2,7 @@ import { QuartzTransformerPluginInstance, QuartzFilterPluginInstance, QuartzEmitterPluginInstance, - QuartzPageTypePluginInstance, + PageTypePluginEntry, } from "./types" import { LoadedPlugin } from "./loader/types" @@ -10,7 +10,7 @@ export interface PluginConfiguration { transformers: (QuartzTransformerPluginInstance | LoadedPlugin)[] filters: (QuartzFilterPluginInstance | LoadedPlugin)[] emitters: (QuartzEmitterPluginInstance | LoadedPlugin)[] - pageTypes?: (QuartzPageTypePluginInstance | LoadedPlugin)[] + pageTypes?: (PageTypePluginEntry | LoadedPlugin)[] } export function isLoadedPlugin(plugin: unknown): plugin is LoadedPlugin { @@ -29,14 +29,14 @@ export function getPluginInstance( | QuartzTransformerPluginInstance | QuartzFilterPluginInstance | QuartzEmitterPluginInstance - | QuartzPageTypePluginInstance + | PageTypePluginEntry | LoadedPlugin, options?: T, ): | QuartzTransformerPluginInstance | QuartzFilterPluginInstance | QuartzEmitterPluginInstance - | QuartzPageTypePluginInstance { + | PageTypePluginEntry { if (isLoadedPlugin(plugin)) { const factory = plugin.plugin as ( opts?: T, @@ -44,7 +44,7 @@ export function getPluginInstance( | QuartzTransformerPluginInstance | QuartzFilterPluginInstance | QuartzEmitterPluginInstance - | QuartzPageTypePluginInstance + | PageTypePluginEntry return factory(options) } return plugin diff --git a/quartz/plugins/types.ts b/quartz/plugins/types.ts index 6926d45eb..24bb5443e 100644 --- a/quartz/plugins/types.ts +++ b/quartz/plugins/types.ts @@ -2,7 +2,7 @@ import { PluggableList } from "unified" import { StaticResources } from "../util/resources" import { ProcessedContent, QuartzPluginData } from "./vfile" import { QuartzComponent, QuartzComponentConstructor } from "../components/types" -import { FilePath, FullSlug } from "../util/path" +import { FilePath } from "../util/path" import { BuildCtx } from "../util/ctx" import { GlobalConfiguration } from "../cfg" import { VFile } from "vfile" @@ -11,7 +11,7 @@ export interface PluginTypes { transformers: QuartzTransformerPluginInstance[] filters: QuartzFilterPluginInstance[] emitters: QuartzEmitterPluginInstance[] - pageTypes?: QuartzPageTypePluginInstance[] + pageTypes?: PageTypePluginEntry[] } type OptionType = object | undefined @@ -70,62 +70,50 @@ export type QuartzEmitterPluginInstance = { // PageType Plugin Types // ============================================================================ -/** - * Matcher function: determines if a source file belongs to a page type. - * Returns true if the page type should own this file. - */ export type PageMatcher = (args: { - slug: FullSlug + slug: string fileData: QuartzPluginData cfg: GlobalConfiguration + [key: string]: unknown }) => boolean -/** - * Virtual page descriptor for page types that generate pages - * from aggregated data (e.g., tag indexes, folder listings). - */ export interface VirtualPage { - slug: FullSlug + slug: string title: string - data: Partial + data: Partial & Record } -/** - * Generator function: produces virtual pages from all processed content. - * Used by page types that don't match source files but instead create - * synthetic pages (e.g., one page per tag, one page per folder). - */ export type PageGenerator = (args: { content: ProcessedContent[] cfg: GlobalConfiguration ctx: BuildCtx + [key: string]: unknown }) => VirtualPage[] -/** - * Factory function that creates a PageType plugin instance. - */ export type QuartzPageTypePlugin = ( opts?: Options, ) => QuartzPageTypePluginInstance -/** - * A PageType plugin instance. - * - * PageTypes are a declarative abstraction over page-rendering emitters. - * Each PageType declares which files it owns (via `match`), optionally - * generates virtual pages (via `generate`), and provides a body component - * and layout reference for rendering. - */ -export type QuartzPageTypePluginInstance = { +export interface QuartzPageTypePluginInstance { name: string - /** Higher priority wins when multiple page types match the same file. Default: 0. */ priority?: number - /** Determines which source files this page type owns. */ match: PageMatcher - /** Produces virtual pages from aggregated content data. */ generate?: PageGenerator - /** Layout key — references a key in `layout.byPageType`. */ layout: string - /** The body component constructor for this page type. */ + body: QuartzComponentConstructor +} + +// Structural supertype accepted in plugin configuration arrays. +// Community plugins use a differently-branded FullSlug in their PageMatcher, +// making them incompatible with the internal PageMatcher under strict +// function-parameter contravariance. This wider entry type avoids forcing +// casts in quartz.config.ts while the dispatcher safely calls match/generate +// with the correct arguments at runtime. +export interface PageTypePluginEntry { + name: string + priority?: number + match: (...args: never[]) => boolean + generate?: (...args: never[]) => VirtualPage[] + layout: string body: QuartzComponentConstructor }