import { Initializer, api, log, utils } from "actionhero"; import { GrouparooPlugin } from "../classes/plugin"; import { plugin } from "../modules/plugin"; import { App } from "../models/App"; import { getPluginManifest } from "../modules/pluginDetails"; import { CLS } from "../modules/cls"; import { getGrouparooRunMode } from "../modules/runMode"; declare module "actionhero" { export interface Api { plugins: { plugins: GrouparooPlugin[]; validate: (plugin: GrouparooPlugin) => boolean; register: (plugin: GrouparooPlugin, validate: boolean) => void; announcePlugins: () => void; persistentConnections: { [id: string]: any; }; }; } } export class Plugins extends Initializer { constructor() { super(); this.name = "plugins"; this.loadPriority = 999; this.startPriority = 1; this.stopPriority = 900; } async initialize() { api.plugins = { plugins: [], persistentConnections: {}, validate: this.validatePlugin, register: this.registerPlugin, announcePlugins: this.announcePlugins, }; this.checkPluginEnvironmentVariables(); // --- Add the core plugins --- // plugin.registerPlugin({ name: "@grouparoo/core", icon: "/public/@grouparoo/logo.png", }); // --- Add the UI pLugin --- // const pluginManifest = getPluginManifest(); const uiPlugins = pluginManifest.plugins .filter((p) => p.name.match(/^@grouparoo\/ui-/)) .sort((p) => (p.name.match("config") ? 1 : -1)); if (uiPlugins) { const relevantUiPlugin = uiPlugins[ getGrouparooRunMode() === "cli:config" ? uiPlugins.length - 1 : 0 ]; if (relevantUiPlugin) { this.registerPlugin({ name: relevantUiPlugin.name, icon: "/public/@grouparoo/logo.png", }); process.env.GROUPAROO_UI_EDITION = relevantUiPlugin.name.replace( "@grouparoo/ui-", "" ); } } } async start() { api.plugins.announcePlugins(); } async stop() { await CLS.wrap(async () => { for (const id in api.plugins.persistentConnections) { const app = await App.findById(id).catch(() => null); await app?.disconnect(); } await utils.sleep(100); }); } validatePlugin(plugin: GrouparooPlugin) { let errors: string[] = []; if (!plugin.name) { errors.push(`name is required for a Grouparoo Plugin`); } if (api.plugins.plugins.map((p) => p.name).includes(plugin.name)) { errors.push(`a plugin named ${plugin.name} is already registered`); } if (plugin.apps) { plugin?.apps.forEach((app) => { if (!app.name) { errors.push(`app.name is required for a Grouparoo Plugin App`); } if ( api.plugins.plugins .map((p) => p.apps?.map((app) => app.name)) .flat() .includes(plugin.name) ) { errors.push(`a plugin app named ${app.name} is already registered`); } if (!app.options) { errors.push(`app.options is required for a Grouparoo Plugin App`); } if (!app.methods.test) { errors.push( `app.methods.test is required for a Grouparoo Plugin App` ); } if ( (app.methods.connect && !app.methods.disconnect) || (!app.methods.connect && app.methods.disconnect) ) { errors.push( `both app.methods.connect and app.methods.disconnect are required for a Grouparoo Plugin App` ); } }); } if (plugin.connections) { plugin.connections.forEach((connection) => { if (!connection.name) { errors.push( `connection.name is required for a Grouparoo Plugin Connection` ); } if ( api.plugins.plugins .map((p) => p.connections?.map((conn) => conn.name)) .flat() .includes(connection.name) ) { errors.push( `a plugin connection named ${connection.name} is already registered` ); } if (!connection.apps || connection.apps.length < 1) { errors.push( `connection.apps is required for a Grouparoo Plugin Connection (${connection.name})` ); } if (!connection.description) { errors.push( `connection.description is required for a Grouparoo Plugin Connection (${connection.name})` ); } if (!connection.direction) { errors.push( `connection.direction is required for a Grouparoo Plugin Connection (${connection.name})` ); } if (!connection.options) { errors.push( `connection.options is required for a Grouparoo Plugin Connection (${connection.name})` ); } if (!["import", "export"].includes(connection.direction)) { errors.push( `connection.direction must be either import or export for a Grouparoo Plugin Connection (${connection.name})` ); } if ( connection.direction === "import" && !connection.methods.importRecords && !connection.methods.recordProperty && !connection.methods.recordProperties ) { errors.push( `import connections must provide a way to import records (${connection.name})` ); } if ( connection.direction === "import" && connection.methods.importRecords && connection.supportIncrementalSchedule === undefined ) { errors.push( `import connections that support schedules must declare supportIncrementalSchedule` ); } if ( connection.direction === "export" && !connection.methods.exportRecord && !connection.methods.exportRecords ) { errors.push( `export connections must provide either connection.methods.exportRecord or connection.methods.exportRecords (${connection.name})` ); } }); } if (errors.length > 0) { throw new Error( `Plugin validation failure for ${ plugin.name ? `"${plugin.name}"` : "plugin" }: ${errors.join(", ")}` ); } return true; } registerPlugin(plugin: GrouparooPlugin, validate = true) { if (validate) api.plugins.validate(plugin); api.plugins.plugins.push(plugin); } announcePlugins() { log(`active plugins: ${api.plugins.plugins.map((p) => p.name).join(", ")}`); } /* * ensure that the ENV variables for the plugins are set */ checkPluginEnvironmentVariables() { const pluginManifest = getPluginManifest(); pluginManifest.plugins.forEach((plugin) => { if (plugin?.grouparoo?.env?.api) { plugin.grouparoo.env.api.forEach((e) => { if (!process.env[e]) { log( `Plugin ${plugin.name} requires environment variable ${e} to be set`, "warning" ); } }); } if (plugin?.grouparoo?.env?.web) { plugin.grouparoo.env.web.forEach((e) => { if (!process.env[e]) { log( `plugin ${plugin.name} requires environment variable ${e} to be set`, "warning" ); } }); } }); } }