A simple, folder-driven static-site engine.
bun ssg fs
at dev 128 lines 3.8 kB view raw
1/** 2 * Plugin runner: executes lifecycle hooks and transform hooks in order. 3 */ 4 5import type { Block, Collection, Entry, Site } from "../core/model"; 6import type { LoadedPlugin, PluginContext } from "./types"; 7import Handlebars from "handlebars"; 8 9const logError = (logger: PluginContext["logger"], pluginId: string, hook: string, error: unknown) => { 10 logger.error("plugin.hookError", { pluginId, hook, error: String(error) }); 11}; 12 13// Lifecycle hooks onInit/onScan/onResolved 14export async function runOnInit(plugins: LoadedPlugin[], ctx: PluginContext) { 15 for (const plugin of plugins) { 16 if (!plugin.onInit) continue; 17 try { 18 await plugin.onInit({ ...ctx, pluginId: plugin.pluginId }); 19 } catch (err) { 20 logError(ctx.logger, plugin.pluginId, "onInit", err); 21 } 22 } 23} 24 25export async function runOnScan(plugins: LoadedPlugin[], site: Site, ctx: PluginContext): Promise<Site> { 26 let current = site; 27 for (const plugin of plugins) { 28 if (!plugin.onScan) continue; 29 try { 30 const result = await plugin.onScan(current, { ...ctx, pluginId: plugin.pluginId }); 31 if (result && typeof result === "object") { 32 current = result as Site; 33 } 34 } catch (err) { 35 logError(ctx.logger, plugin.pluginId, "onScan", err); 36 } 37 } 38 return current; 39} 40 41export async function runOnResolved(plugins: LoadedPlugin[], site: Site, ctx: PluginContext): Promise<Site> { 42 let current = site; 43 for (const plugin of plugins) { 44 if (!plugin.onResolved) continue; 45 try { 46 const result = await plugin.onResolved(current, { ...ctx, pluginId: plugin.pluginId }); 47 if (result && typeof result === "object") { 48 current = result as Site; 49 } 50 } catch (err) { 51 logError(ctx.logger, plugin.pluginId, "onResolved", err); 52 } 53 } 54 return current; 55} 56 57export async function runTransformBlocks( 58 plugins: LoadedPlugin[], 59 collection: Collection, 60 entry: Entry, 61 ctx: PluginContext 62): Promise<Block[]> { 63 // Depth-first transform with recursion on subBlocks 64 const transformBlock = async (blk: Block): Promise<Block> => { 65 let current = blk; 66 for (const plugin of plugins) { 67 if (!plugin.transformBlock) continue; 68 try { 69 const res = await plugin.transformBlock(current, { 70 ...ctx, 71 entry, 72 collection, 73 pluginId: plugin.pluginId, 74 }); 75 if (res && typeof res === "object") { 76 current = res as Block; 77 } 78 } catch (err) { 79 logError(ctx.logger, plugin.pluginId, "transformBlock", err); 80 } 81 } 82 83 if (current.subBlocks && current.subBlocks.length) { 84 const updatedSubs = await Promise.all(current.subBlocks.map(transformBlock)); 85 current = { ...current, subBlocks: updatedSubs }; 86 } 87 88 return current; 89 }; 90 91 return Promise.all(entry.blocks.map(transformBlock)); 92} 93 94export async function runTransformEntry( 95 plugins: LoadedPlugin[], 96 collection: Collection, 97 entry: Entry, 98 ctx: PluginContext 99): Promise<Entry> { 100 let current = entry; 101 for (const plugin of plugins) { 102 if (!plugin.transformEntry) continue; 103 try { 104 const res = await plugin.transformEntry(current, { 105 ...ctx, 106 collection, 107 pluginId: plugin.pluginId, 108 }); 109 if (res && typeof res === "object") { 110 current = res as Entry; 111 } 112 } catch (err) { 113 logError(ctx.logger, plugin.pluginId, "transformEntry", err); 114 } 115 } 116 return current; 117} 118 119export async function runExtendTemplates(plugins: LoadedPlugin[], ctx: PluginContext) { 120 for (const plugin of plugins) { 121 if (!plugin.extendTemplates) continue; 122 try { 123 await plugin.extendTemplates(Handlebars, { ...ctx, pluginId: plugin.pluginId }); 124 } catch (err) { 125 logError(ctx.logger, plugin.pluginId, "extendTemplates", err); 126 } 127 } 128}