Files
scoreodonto.com/backend/dist/core/plugin-loader.js
T

133 lines
5.7 KiB
JavaScript

"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.loadPlugins = loadPlugins;
const path_1 = __importDefault(require("path"));
const fs_1 = __importDefault(require("fs"));
const logger_1 = require("../config/logger");
const PLUGINS_DIR = process.env.NODE_ENV === 'production'
? '/app/plugins'
: path_1.default.resolve(__dirname, '../../../plugins');
/**
* Ordena plugins por dependências usando topological sort (Kahn's algorithm).
* Plugins sem dependências vêm primeiro; core plugins antes dos business plugins.
*/
function topoSort(plugins) {
const byName = new Map(plugins.map((p) => [p.manifest.name, p]));
const inDegree = new Map();
const dependents = new Map();
for (const p of plugins) {
inDegree.set(p.manifest.name, 0);
dependents.set(p.manifest.name, []);
}
for (const p of plugins) {
for (const dep of p.manifest.dependencies) {
if (!byName.has(dep)) {
logger_1.logger.warn({ plugin: p.manifest.name, dep }, '[PluginLoader] Dependência não encontrada');
continue;
}
dependents.get(dep).push(p.manifest.name);
inDegree.set(p.manifest.name, (inDegree.get(p.manifest.name) ?? 0) + 1);
}
}
const queue = plugins
.filter((p) => inDegree.get(p.manifest.name) === 0)
.sort((a, b) => (a.manifest.category === 'core' ? -1 : 1) - (b.manifest.category === 'core' ? -1 : 1));
const sorted = [];
while (queue.length > 0) {
const current = queue.shift();
sorted.push(current);
for (const depName of dependents.get(current.manifest.name) ?? []) {
const newDegree = (inDegree.get(depName) ?? 1) - 1;
inDegree.set(depName, newDegree);
if (newDegree === 0) {
queue.push(byName.get(depName));
}
}
}
if (sorted.length !== plugins.length) {
logger_1.logger.error('[PluginLoader] Dependência circular detectada — carregando na ordem original');
return plugins;
}
return sorted;
}
/**
* Escaneia o diretório /plugins, lê cada manifest.json e importa index.ts/js.
* Retorna plugins ordenados por dependência.
*/
async function loadPlugins() {
if (!fs_1.default.existsSync(PLUGINS_DIR)) {
logger_1.logger.warn({ dir: PLUGINS_DIR }, '[PluginLoader] Diretório plugins/ não encontrado');
return [];
}
const entries = fs_1.default.readdirSync(PLUGINS_DIR, { withFileTypes: true });
const pluginDirs = entries.filter((e) => e.isDirectory()).map((e) => e.name);
const loaded = [];
for (const dirName of pluginDirs) {
const pluginDir = path_1.default.join(PLUGINS_DIR, dirName);
const manifestPath = path_1.default.join(pluginDir, 'manifest.json');
const indexTs = path_1.default.join(pluginDir, 'index.ts');
const indexJs = path_1.default.join(pluginDir, 'index.js');
if (!fs_1.default.existsSync(manifestPath)) {
logger_1.logger.warn({ dir: dirName }, '[PluginLoader] manifest.json não encontrado — pulando');
continue;
}
// Prefere .js (produção compilada); fallback para .ts (tsx watch em dev)
const entryPoint = fs_1.default.existsSync(indexJs) ? indexJs : fs_1.default.existsSync(indexTs) ? indexTs : null;
if (!entryPoint) {
logger_1.logger.warn({ dir: dirName }, '[PluginLoader] index.ts/js não encontrado — pulando');
continue;
}
try {
const manifest = JSON.parse(fs_1.default.readFileSync(manifestPath, 'utf-8'));
const mod = await Promise.resolve(`${entryPoint}`).then(s => __importStar(require(s)));
const instance = mod.default ?? mod;
if (typeof instance.activate !== 'function') {
logger_1.logger.warn({ dir: dirName }, '[PluginLoader] Plugin sem método activate() — pulando');
continue;
}
loaded.push({ instance, manifest, dir: pluginDir });
logger_1.logger.info({ name: manifest.name, version: manifest.version }, '[PluginLoader] Plugin carregado');
}
catch (err) {
logger_1.logger.error({ err, dir: dirName }, '[PluginLoader] Falha ao carregar plugin');
}
}
return topoSort(loaded);
}