import { CommaSeparatedStringArray, Config, Env } from '@n8n/config'; import type { InstanceSettings } from 'n8n-core'; import { UnexpectedError } from 'n8n-workflow'; export type ModulePreInitContext = { instance: InstanceSettings; }; export type ModulePreInit = { shouldLoadModule: (ctx: ModulePreInitContext) => boolean; }; const moduleNames = ['insights', 'external-secrets'] as const; export type ModuleName = (typeof moduleNames)[number]; class Modules extends CommaSeparatedStringArray { constructor(str: string) { super(str); for (const moduleName of this) { if (!moduleNames.includes(moduleName)) { throw new UnexpectedError(`Unknown module name ${moduleName}`, { level: 'fatal' }); } } } } @Config export class ModulesConfig { /** Comma-separated list of all modules enabled */ @Env('N8N_ENABLED_MODULES') enabledModules: Modules = []; /** Comma-separated list of all disabled modules */ @Env('N8N_DISABLED_MODULES') disabledModules: Modules = []; // Default modules are always enabled unless explicitly disabled private readonly defaultModules: ModuleName[] = ['insights', 'external-secrets']; // Loaded modules are the ones that have been loaded so far by the instance readonly loadedModules = new Set(); // Get all modules by merging default and enabled, and filtering out disabled modules get modules(): ModuleName[] { if (this.enabledModules.some((module) => this.disabledModules.includes(module))) { throw new UnexpectedError('Module cannot be both enabled and disabled', { level: 'fatal' }); } const enabledModules = Array.from(new Set(this.defaultModules.concat(this.enabledModules))); return enabledModules.filter((module) => !this.disabledModules.includes(module)); } addLoadedModule(module: ModuleName) { this.loadedModules.add(module); } }