Barazo AppView backend barazo.forum
at main 125 lines 4.2 kB view raw
1import { join } from 'node:path' 2 3import type { Logger } from '../logger.js' 4 5import type { PluginContext, PluginHooks, LoadedPlugin } from './types.js' 6import type { PluginManifest } from '../../validation/plugin-manifest.js' 7 8// --------------------------------------------------------------------------- 9// Hook reference parsing 10// --------------------------------------------------------------------------- 11 12export function resolveHookRef(ref: string): { modulePath: string; exportName: string } | null { 13 const hashIndex = ref.indexOf('#') 14 if (hashIndex <= 0) return null 15 return { 16 modulePath: ref.slice(0, hashIndex), 17 exportName: ref.slice(hashIndex + 1), 18 } 19} 20 21// --------------------------------------------------------------------------- 22// Plugin short name 23// --------------------------------------------------------------------------- 24 25export function getPluginShortName(name: string): string { 26 if (name.startsWith('@barazo/plugin-')) return name.slice('@barazo/plugin-'.length) 27 if (name.startsWith('barazo-plugin-')) return name.slice('barazo-plugin-'.length) 28 return name 29} 30 31// --------------------------------------------------------------------------- 32// Hook execution 33// --------------------------------------------------------------------------- 34 35export async function executeHook( 36 hookName: string, 37 hookFn: (...args: unknown[]) => Promise<void> | void, 38 ctx: PluginContext, 39 logger: Logger, 40 pluginName: string, 41 ...extraArgs: unknown[] 42): Promise<boolean> { 43 try { 44 await hookFn(ctx, ...extraArgs) 45 logger.info({ plugin: pluginName, hook: hookName }, 'Plugin hook executed') 46 return true 47 } catch (err: unknown) { 48 logger.error({ err, plugin: pluginName, hook: hookName }, 'Plugin hook failed') 49 return false 50 } 51} 52 53// --------------------------------------------------------------------------- 54// Load hooks from manifest 55// --------------------------------------------------------------------------- 56 57const HOOK_NAMES = ['onInstall', 'onUninstall', 'onEnable', 'onDisable', 'onProfileSync'] as const 58 59export async function loadPluginHooks( 60 packagePath: string, 61 manifest: PluginManifest, 62 logger: Logger 63): Promise<PluginHooks> { 64 const hooks: PluginHooks = {} 65 const hookEntries = manifest.hooks 66 if (!hookEntries) return hooks 67 68 for (const name of HOOK_NAMES) { 69 const ref = hookEntries[name] 70 if (!ref) continue 71 72 const parsed = resolveHookRef(ref) 73 if (!parsed) { 74 logger.warn({ plugin: manifest.name, hook: name, ref }, 'Invalid hook reference, skipping') 75 continue 76 } 77 78 try { 79 const fullPath = join(packagePath, parsed.modulePath) 80 const mod = (await import(fullPath)) as Record<string, unknown> 81 const fn = mod[parsed.exportName] 82 if (typeof fn === 'function') { 83 // Each hook is assigned individually after type-checking the export. 84 ;(hooks as Record<string, unknown>)[name] = fn 85 } else { 86 logger.warn( 87 { plugin: manifest.name, hook: name, export: parsed.exportName }, 88 'Hook export is not a function' 89 ) 90 } 91 } catch (err: unknown) { 92 logger.error({ err, plugin: manifest.name, hook: name }, 'Failed to load hook module') 93 } 94 } 95 96 return hooks 97} 98 99// --------------------------------------------------------------------------- 100// Build LoadedPlugin from discovery result 101// --------------------------------------------------------------------------- 102 103export async function buildLoadedPlugin( 104 manifest: PluginManifest, 105 packagePath: string, 106 logger: Logger 107): Promise<LoadedPlugin> { 108 const hooks = await loadPluginHooks(packagePath, manifest, logger) 109 110 return { 111 name: manifest.name, 112 displayName: manifest.displayName, 113 version: manifest.version, 114 description: manifest.description, 115 source: manifest.source, 116 category: manifest.category, 117 manifest: manifest as unknown as Record<string, unknown>, 118 packagePath, 119 hooks, 120 ...(manifest.backend?.routes !== undefined && { routesPath: manifest.backend.routes }), 121 ...(manifest.backend?.migrations !== undefined && { 122 migrationsPath: manifest.backend.migrations, 123 }), 124 } 125}