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.
This commit is contained in:
saberzero1 2026-02-13 19:34:56 +01:00
parent 68f3c3fadd
commit fd36066fdf
No known key found for this signature in database
2 changed files with 28 additions and 40 deletions

View File

@ -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<T extends object | undefined>(
| 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<T extends object | undefined>(
| QuartzTransformerPluginInstance
| QuartzFilterPluginInstance
| QuartzEmitterPluginInstance
| QuartzPageTypePluginInstance
| PageTypePluginEntry
return factory(options)
}
return plugin

View File

@ -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<QuartzPluginData>
data: Partial<QuartzPluginData> & Record<string, unknown>
}
/**
* 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<Options extends OptionType = undefined> = (
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
}