@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
JavaScript
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;
}