Initial commit

This commit is contained in:
Pax
2026-07-07 20:48:21 -04:00
committed by GitHub
commit ceb2d21bbf
292 changed files with 42584 additions and 0 deletions

View File

@@ -0,0 +1,499 @@
import { styleText } from "util"
import {
PluginManifest,
PluginCategory,
LoadedPlugin,
PluginResolution,
PluginResolutionError,
PluginResolutionOptions,
PluginSpecifier,
} from "./types"
import {
QuartzTransformerPlugin,
QuartzFilterPlugin,
QuartzEmitterPlugin,
QuartzPageTypePlugin,
} from "../types"
import {
parsePluginSource,
installPlugin,
getPluginEntryPoint,
toFileUrl,
isLocalSource,
validatePluginExternals,
} from "./gitLoader"
const MINIMUM_QUARTZ_VERSION = "4.5.0"
function satisfiesVersion(required: string | undefined, current: string): boolean {
if (!required) return true
const parseVersion = (v: string) => {
const parts = v.replace(/^v/, "").split(".")
return {
major: parseInt(parts[0]) || 0,
minor: parseInt(parts[1]) || 0,
patch: parseInt(parts[2]) || 0,
}
}
const req = parseVersion(required)
const cur = parseVersion(current)
if (cur.major > req.major) return true
if (cur.major < req.major) return false
if (cur.minor > req.minor) return true
if (cur.minor < req.minor) return false
return cur.patch >= req.patch
}
async function tryImportPlugin(packageName: string): Promise<{
module: unknown
manifest: PluginManifest | null
}> {
try {
const module = await import(packageName)
const manifest: PluginManifest | null = module.manifest ?? null
return { module, manifest }
} catch (error) {
throw new Error(
`Failed to import package: ${error instanceof Error ? error.message : String(error)}`,
)
}
}
function detectPluginType(
module: unknown,
): "transformer" | "filter" | "emitter" | "pageType" | null {
if (!module || typeof module !== "object") return null
const mod = module as Record<string, unknown>
if (typeof mod.default === "function") {
return null
}
const hasPageTypeProps = ["match", "body", "layout"].every((key) => key in mod)
const hasTransformerProps = ["textTransform", "markdownPlugins", "htmlPlugins"].some(
(key) => key in mod && (typeof mod[key] === "function" || mod[key] === undefined),
)
const hasFilterProps = ["shouldPublish"].some(
(key) => key in mod && typeof mod[key] === "function",
)
const hasEmitterProps = ["emit"].some((key) => key in mod && typeof mod[key] === "function")
if (hasPageTypeProps) return "pageType"
if (hasEmitterProps) return "emitter"
if (hasFilterProps) return "filter"
if (hasTransformerProps) return "transformer"
return null
}
function extractPluginFactory(
module: unknown,
type: "transformer" | "filter" | "emitter" | "pageType",
):
| QuartzTransformerPlugin
| QuartzFilterPlugin
| QuartzEmitterPlugin
| QuartzPageTypePlugin
| null {
if (!module || typeof module !== "object") return null
const mod = module as Record<string, unknown>
const factory = mod.default ?? mod[type] ?? mod.plugin ?? null
if (typeof factory === "function") {
return factory as
| QuartzTransformerPlugin
| QuartzFilterPlugin
| QuartzEmitterPlugin
| QuartzPageTypePlugin
}
return null
}
function isGitSource(source: string): boolean {
// Check if it's a Git-based or local file path source
return (
isLocalSource(source) ||
source.startsWith("github:") ||
source.startsWith("git+") ||
source.startsWith("https://github.com/") ||
source.startsWith("https://gitlab.com/") ||
source.startsWith("https://bitbucket.org/")
)
}
async function resolveSinglePlugin(
specifier: PluginSpecifier,
options: PluginResolutionOptions,
): Promise<{ plugin: LoadedPlugin | null; error: PluginResolutionError | null }> {
let packageName: string
let manifest: Partial<PluginManifest> = {}
let pluginSource = "npm"
if (typeof specifier === "string") {
packageName = specifier
// Check if it's a Git-based source
if (isGitSource(specifier)) {
pluginSource = "git"
}
} else if ("name" in specifier) {
packageName = specifier.name
if (isGitSource(specifier.name)) {
pluginSource = "git"
}
} else if ("plugin" in specifier) {
const rawType = specifier.manifest?.category ?? "transformer"
const type = Array.isArray(rawType) ? rawType[0] : rawType
return {
plugin: {
plugin: specifier.plugin as QuartzTransformerPlugin,
manifest: {
name: specifier.manifest?.name ?? "inline-plugin",
displayName: specifier.manifest?.displayName ?? "Inline Plugin",
description: specifier.manifest?.description ?? "Inline plugin instance",
version: specifier.manifest?.version ?? "1.0.0",
category: rawType,
...specifier.manifest,
} as PluginManifest,
type,
source: "inline",
},
error: null,
}
} else {
return {
plugin: null,
error: {
plugin: "unknown",
message: "Invalid plugin specifier format",
type: "invalid-manifest",
},
}
}
if (pluginSource === "git") {
try {
const gitSpec = parsePluginSource(packageName)
await installPlugin(gitSpec, { verbose: options.verbose })
const entryPoint = getPluginEntryPoint(gitSpec.name)
// Import the plugin
const module = await import(toFileUrl(entryPoint))
const importedManifest: PluginManifest | null = module.manifest ?? null
validatePluginExternals(gitSpec.name, entryPoint, { verbose: options.verbose })
manifest = importedManifest ?? {}
const categoryOrCategories = manifest.category ?? detectPluginType(module)
if (!categoryOrCategories) {
return {
plugin: null,
error: {
plugin: packageName,
message: "Could not detect plugin type from Git source",
type: "invalid-manifest",
},
}
}
// Normalize to single processing category for factory extraction
const processingCategories = ["transformer", "filter", "emitter", "pageType"] as const
type ProcessingCategory = (typeof processingCategories)[number]
const detectedType: PluginCategory = Array.isArray(categoryOrCategories)
? categoryOrCategories[0]
: categoryOrCategories
const processingType: ProcessingCategory | undefined = Array.isArray(categoryOrCategories)
? (categoryOrCategories.find((c) =>
(processingCategories as readonly string[]).includes(c),
) as ProcessingCategory | undefined)
: (processingCategories as readonly string[]).includes(categoryOrCategories)
? (categoryOrCategories as ProcessingCategory)
: undefined
// Component-only plugins don't have a processing factory
if (!processingType) {
const fullManifest: PluginManifest = {
name: manifest.name ?? gitSpec.name,
displayName: manifest.displayName ?? gitSpec.name,
description: manifest.description ?? "No description provided",
version: manifest.version ?? "1.0.0",
author: manifest.author,
homepage: manifest.homepage,
keywords: manifest.keywords,
category: manifest.category ?? detectedType,
quartzVersion: manifest.quartzVersion,
configSchema: manifest.configSchema,
}
if (options.verbose) {
console.log(
styleText("green", `\u2713`) +
` Loaded ${detectedType} plugin: ${styleText("cyan", fullManifest.displayName)}@${fullManifest.version} ${styleText("gray", `(from ${gitSpec.repo})`)}`,
)
}
return { plugin: null, error: null }
}
const factory = extractPluginFactory(module, processingType)
if (!factory) {
return {
plugin: null,
error: {
plugin: packageName,
message: "Could not find plugin factory in Git source",
type: "invalid-manifest",
},
}
}
const fullManifest: PluginManifest = {
name: manifest.name ?? gitSpec.name,
displayName: manifest.displayName ?? gitSpec.name,
description: manifest.description ?? "No description provided",
version: manifest.version ?? "1.0.0",
author: manifest.author,
homepage: manifest.homepage,
keywords: manifest.keywords,
category: manifest.category ?? detectedType,
quartzVersion: manifest.quartzVersion,
configSchema: manifest.configSchema,
}
const loadedPlugin: LoadedPlugin = {
plugin: factory,
manifest: fullManifest,
type: detectedType,
source: gitSpec.local ? `local:${gitSpec.repo}` : `${gitSpec.repo}#${gitSpec.ref}`,
}
if (options.verbose) {
console.log(
styleText("green", ``) +
` Loaded ${detectedType} plugin: ${styleText("cyan", fullManifest.displayName)}@${fullManifest.version} ${styleText("gray", `(from ${gitSpec.repo})`)}`,
)
}
return { plugin: loadedPlugin, error: null }
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error)
return {
plugin: null,
error: {
plugin: packageName,
message: `Failed to load Git plugin: ${errorMessage}`,
type: "import-error",
},
}
}
}
try {
const { module: importedModule, manifest: importedManifest } =
await tryImportPlugin(packageName)
manifest = importedManifest ?? {}
// Load components if the plugin declares any
if (manifest.components && Object.keys(manifest.components).length > 0) {
const { loadComponentsFromPackage } = await import("./componentLoader")
await loadComponentsFromPackage(packageName, manifest as PluginManifest)
}
const categoryOrCategories = manifest.category ?? detectPluginType(importedModule)
if (!categoryOrCategories) {
return {
plugin: null,
error: {
plugin: packageName,
message: `Could not detect plugin type. Ensure the plugin exports a valid factory function or has a 'category' field in its manifest.`,
type: "invalid-manifest",
},
}
}
// Normalize to single processing category for factory extraction
const processingCategories = ["transformer", "filter", "emitter", "pageType"] as const
type ProcessingCategory = (typeof processingCategories)[number]
const detectedType: PluginCategory = Array.isArray(categoryOrCategories)
? categoryOrCategories[0]
: categoryOrCategories
const processingType: ProcessingCategory | undefined = Array.isArray(categoryOrCategories)
? (categoryOrCategories.find((c) =>
(processingCategories as readonly string[]).includes(c),
) as ProcessingCategory | undefined)
: (processingCategories as readonly string[]).includes(categoryOrCategories)
? (categoryOrCategories as ProcessingCategory)
: undefined
if (
manifest.quartzVersion &&
!satisfiesVersion(manifest.quartzVersion, options.quartzVersion)
) {
return {
plugin: null,
error: {
plugin: packageName,
message: `Plugin requires Quartz ${manifest.quartzVersion} but current version is ${options.quartzVersion}`,
type: "version-mismatch",
},
}
}
// Component-only plugins don't have a processing factory
if (!processingType) {
const fullManifest: PluginManifest = {
name: manifest.name ?? packageName,
displayName: manifest.displayName ?? packageName,
description: manifest.description ?? "No description provided",
version: manifest.version ?? "1.0.0",
author: manifest.author,
homepage: manifest.homepage,
keywords: manifest.keywords,
category: manifest.category ?? detectedType,
quartzVersion: manifest.quartzVersion,
configSchema: manifest.configSchema,
}
if (options.verbose) {
console.log(
styleText("green", `\u2713`) +
` Loaded ${detectedType} plugin: ${styleText("cyan", fullManifest.displayName)}@${fullManifest.version}`,
)
}
return { plugin: null, error: null }
}
const factory = extractPluginFactory(importedModule, processingType)
if (!factory) {
return {
plugin: null,
error: {
plugin: packageName,
message: `Could not find plugin factory in module. Expected 'export default' or '${processingType}' export.`,
type: "invalid-manifest",
},
}
}
const fullManifest: PluginManifest = {
name: manifest.name ?? packageName,
displayName: manifest.displayName ?? packageName,
description: manifest.description ?? "No description provided",
version: manifest.version ?? "1.0.0",
author: manifest.author,
homepage: manifest.homepage,
keywords: manifest.keywords,
category: manifest.category ?? detectedType,
quartzVersion: manifest.quartzVersion,
configSchema: manifest.configSchema,
}
const loadedPlugin: LoadedPlugin = {
plugin: factory,
manifest: fullManifest,
type: detectedType,
source: packageName,
}
if (options.verbose) {
console.log(
styleText("green", ``) +
` Loaded ${detectedType} plugin: ${styleText("cyan", fullManifest.displayName)}@${fullManifest.version}`,
)
}
return { plugin: loadedPlugin, error: null }
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error)
if (errorMessage.includes("Cannot find module") || errorMessage.includes("MODULE_NOT_FOUND")) {
return {
plugin: null,
error: {
plugin: packageName,
message: `Plugin package not found. Run 'npm install ${packageName}' to install it.`,
type: "not-found",
},
}
}
return {
plugin: null,
error: {
plugin: packageName,
message: errorMessage,
type: "import-error",
},
}
}
}
export async function resolvePlugins(
specifiers: PluginSpecifier[],
options: PluginResolutionOptions,
): Promise<PluginResolution> {
const plugins: LoadedPlugin[] = []
const errors: PluginResolutionError[] = []
if (options.verbose) {
console.log(styleText("cyan", `Resolving ${specifiers.length} external plugin(s)...`))
}
for (const specifier of specifiers) {
const { plugin, error } = await resolveSinglePlugin(specifier, options)
if (plugin) {
plugins.push(plugin)
} else if (error) {
errors.push(error)
console.error(
styleText("red", ``) +
` Failed to load plugin: ${styleText("yellow", error.plugin)}\n` +
` ${error.message}`,
)
}
}
if (options.verbose && plugins.length > 0) {
const byType = plugins.reduce(
(acc, p) => {
acc[p.type] = (acc[p.type] || 0) + 1
return acc
},
{} as Record<string, number>,
)
console.log(
styleText("cyan", `External plugins loaded:`) +
` ${byType.transformer ?? 0} transformers, ${byType.filter ?? 0} filters, ${byType.emitter ?? 0} emitters, ${byType.pageType ?? 0} pageTypes`,
)
}
return { plugins, errors }
}
export function instantiatePlugin<T>(
loadedPlugin: LoadedPlugin,
options?: T,
): ReturnType<typeof loadedPlugin.plugin> {
const factory = loadedPlugin.plugin as (opts?: T) => ReturnType<typeof loadedPlugin.plugin>
return factory(options)
}
export { satisfiesVersion, MINIMUM_QUARTZ_VERSION }