UNPKG

@hoover-institution/hubspot-lib

Version:

A toolkit for deep integration with HubSpot's Marketing Events API with a plugin-based architecture.

114 lines (94 loc) 3.28 kB
import fs from "fs"; import path from "path"; import { fileURLToPath } from "url"; import { findUpSync } from "find-up"; const RESERVED_PLUGINS = new Set(["ALL"]); /** Absolute path to this file */ const __filename = fileURLToPath(import.meta.url); /** Directory containing this file */ const __dirname = path.dirname(__filename); /** Global plugin registry (autofilled by loader) */ globalThis.__PLUGINS ??= {}; /** Absolute path to built-in plugin directory within the SDK */ const SDK_PLUGIN_DIR = path.resolve(__dirname, "../plugins"); /** * Reads the nearest package.json and resolves the consumer-defined pluginDir (if configured). * * @returns {string | null} Absolute path to custom plugin dir, or null if not set */ function getCustomPluginDir() { const pkgPath = findUpSync("package.json"); if (!pkgPath) return null; const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8")); const dir = pkg["hubspot-lib"]?.pluginDir; if (typeof dir === "string" && dir.trim()) { const pkgRoot = path.dirname(pkgPath); console.log( `ℹ️ Using custom plugin directory: ${path.resolve(pkgRoot, dir)}` ); return path.resolve(pkgRoot, dir); } return null; } /** All valid plugin search paths (native + custom) */ const allPluginPaths = [SDK_PLUGIN_DIR, getCustomPluginDir()].filter(Boolean); /** * Dynamically loads plugin files by name from the SDK or user-defined directories. * Automatically registers plugin names to `globalThis.__PLUGINS`. * Skips plugins already registered inline via `createPlugin`. * * @param {string[]} pluginNames - Array of plugin names to load from disk * @returns {Promise<void>} Resolves when all plugins have been loaded or skipped * * @example * await loadPlugins(["MONGO_SYNC", "MY_CUSTOM_PLUGIN"]); */ export async function loadPlugins(pluginNames = []) { globalThis.__PLUGINS ??= {}; for (const name of pluginNames) { globalThis.__PLUGINS[name] = name; if (RESERVED_PLUGINS.has(name)) { continue; // Skip reserved/built-in plugins like "ALL" } if ( typeof globalThis.__registeredInlinePlugins === "object" && globalThis.__registeredInlinePlugins[name] ) { console.log( `✅ Plugin "${name}" already registered inline — skipping file load.` ); continue; } let found = false; for (const dir of allPluginPaths) { const filePath = path.join(dir, `${name}.js`); if (!fs.existsSync(filePath)) continue; try { const url = pathToImportURL(filePath); await import(url); const source = dir === SDK_PLUGIN_DIR ? "NATIVE" : "EXTERNAL"; console.log(`🔌 Loaded ${source} plugin: ${name}`); found = true; break; } catch (err) { console.error(`❌ Failed to load plugin "${name}":`, err); found = true; break; } } if (!found) { console.log( `ℹ️ Skipping plugin "${name}" — no matching file found (may be custom).` ); } } } /** * Converts a local filesystem path to an importable file URL. * * @param {string} p - Absolute file path * @returns {string} Importable `file://` URL */ function pathToImportURL(p) { return new URL(`file://${path.resolve(p)}`).href; }