UNPKG

@dbs-portal/core-module-registry

Version:

Core module registry system for automatic module discovery and registration

383 lines (382 loc) 13 kB
var __defProp = Object.defineProperty; var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value); import { ModuleStatus, validateModuleMetadata } from "./index.js"; import { u as createLogger, c as getPlatformInfo, v as hasFeature, d as createPlatformError, g as getPath, b as getGlob, E as ENV, a as getFs } from "./platform-utils-BMAGIKY3.js"; class ModuleDiscoveryService { constructor(options = {}) { __publicField(this, "options"); __publicField(this, "runtimeModules", []); __publicField(this, "logger", createLogger("discovery")); __publicField(this, "platformInfo", getPlatformInfo()); this.options = { packageScanPaths: ["packages/modules/*", "packages/core/*"], configScanPaths: ["packages/modules/*", "src/pages/modules/*"], packageNamePattern: "@dbs-portal/module-*", configFilePattern: "module.config.{ts,js}", enablePackageScanning: true, enableConfigDetection: true, enableRuntimeRegistration: true, validateModules: true, ignorePatterns: ["node_modules/**", "dist/**", "**/*.test.*", "**/*.spec.*"], ...options }; this.logger.debug("ModuleDiscoveryService initialized", { platform: this.platformInfo.platform, features: this.platformInfo.features, options: this.options }); } /** * Discover modules from all enabled sources */ async discoverModules() { const startTime = Date.now(); const errors = []; const warnings = []; const allModules = []; try { const discoveryPromises = []; this.logger.debug("Starting module discovery: ", startTime); if (this.options.enablePackageScanning) { discoveryPromises.push(this.discoverFromPackages().catch((error) => { errors.push({ source: "package-scanning", message: error.message, details: error }); return []; })); } if (this.options.enableConfigDetection) { discoveryPromises.push(this.discoverFromConfigs().catch((error) => { errors.push({ source: "config-detection", message: error.message, details: error }); return []; })); } if (this.options.enableRuntimeRegistration) { discoveryPromises.push(this.discoverFromRuntime().catch((error) => { errors.push({ source: "runtime-registration", message: error.message, details: error }); return []; })); } const results = await Promise.all(discoveryPromises); results.forEach((modules) => allModules.push(...modules)); const uniqueModules = this.deduplicateModules(allModules); if (this.options.validateModules) { return this.validateDiscoveredModules(uniqueModules, errors, warnings); } this.logger.info(`Module discovery completed, found ${uniqueModules.length} modules`, { duration: Date.now() - startTime, modules: uniqueModules.map((m) => m.id) }); return uniqueModules; } catch (error) { console.error("Module discovery failed:", error); throw error; } } /** * Discover modules from package.json files */ async discoverFromPackages() { if (!hasFeature("canScanPackages")) { this.logger.warn("Package scanning not available in current environment"); return []; } const modules = []; try { this.logger.debug("Starting package scanning", { paths: this.options.packageScanPaths }); const packageCandidates = await this.scanWorkspacePackages(); for (const candidate of packageCandidates) { try { const module = await this.loadModuleFromPackage(candidate); if (module) { modules.push(module); this.logger.debug(`Loaded module from package: ${module.id}`); } } catch (error) { this.logger.warn(`Failed to load module from package ${candidate.packageName}:`, error); } } } catch (error) { const platformError = createPlatformError("package-scanning", error, [ "Check that package scan paths are correct", "Ensure packages have valid package.json files", "Verify file system permissions" ]); this.logger.error("Package scanning failed:", platformError); throw platformError; } this.logger.info(`Package scanning completed, found ${modules.length} modules`); return modules; } /** * Discover modules from module.config.ts files */ async discoverFromConfigs() { const modules = []; try { const configFiles = await this.findConfigFiles(); for (const configFile of configFiles) { try { const module = await this.loadModuleFromConfig(configFile); if (module) { modules.push(module); } } catch (error) { console.warn(`Failed to load module from config ${configFile}:`, error); } } } catch (error) { console.error("Config detection failed:", error); throw error; } return modules; } /** * Get modules registered at runtime */ async discoverFromRuntime() { return [...this.runtimeModules]; } /** * Register a module at runtime */ registerRuntimeModule(module) { this.runtimeModules.push(module); } /** * Scan workspace for packages matching the pattern */ async scanWorkspacePackages() { const candidates = []; try { const [path, glob] = await Promise.all([getPath(), getGlob()]); const cwd = ENV.isNode ? process.cwd() : "/"; const rootPackageJson = await this.readPackageJson(cwd); const workspaces = rootPackageJson.workspaces || []; for (const workspacePath of workspaces) { const packagePaths = await glob.glob(`${workspacePath}/package.json`, { cwd, ignore: this.options.ignorePatterns }); for (const packagePath of packagePaths) { try { const fullPath = path.resolve(cwd, packagePath); const packageJson = await this.readPackageJson(path.dirname(fullPath)); if (this.isModulePackage(packageJson)) { candidates.push({ id: this.extractModuleId(packageJson.name), packageName: packageJson.name, version: packageJson.version, path: path.dirname(fullPath), packageJson }); } } catch (error) { this.logger.warn(`Failed to read package at ${packagePath}:`, error); } } } } catch (error) { console.error("Workspace scanning failed:", error); throw error; } return candidates; } /** * Find module configuration files */ async findConfigFiles() { if (!hasFeature("canScanFileSystem")) { this.logger.warn("Config file scanning not available in current environment"); return []; } const configFiles = []; try { const [path, glob] = await Promise.all([getPath(), getGlob()]); const cwd = ENV.isNode ? process.cwd() : "/"; for (const scanPath of this.options.configScanPaths) { const pattern = path.join(scanPath, this.options.configFilePattern); const files = await glob.glob(pattern, { cwd, ignore: this.options.ignorePatterns }); configFiles.push(...files.map((file) => path.resolve(cwd, file))); } } catch (error) { const platformError = createPlatformError("config-file-scanning", error, [ "Check that config scan paths exist", "Verify module.config.ts files are valid", "Ensure proper file system permissions" ]); this.logger.error("Config file scanning failed:", platformError); throw platformError; } return configFiles; } /** * Load module metadata from package candidate */ async loadModuleFromPackage(candidate) { try { const path = await getPath(); const configPath = path.join(candidate.path, "module.config.ts"); try { return await this.loadModuleFromConfig(configPath); } catch { return this.generateMetadataFromPackage(candidate); } } catch (error) { console.warn(`Failed to load module from package ${candidate.packageName}:`, error); return null; } } /** * Load module metadata from configuration file */ async loadModuleFromConfig(configPath) { try { const configModule = await import(configPath); const config = configModule.default || configModule.moduleConfig; if (!config) { throw new Error("No module configuration found in file"); } return config; } catch (error) { console.warn(`Failed to load config from ${configPath}:`, error); return null; } } /** * Generate module metadata from package.json */ generateMetadataFromPackage(candidate) { const pkg = candidate.packageJson; return { id: candidate.id, name: this.formatModuleName(candidate.id), version: pkg.version || "1.0.0", description: pkg.description || `${candidate.id} module`, category: this.inferCategoryFromId(candidate.id), tags: pkg.keywords || [], icon: "AppstoreOutlined", permissions: [], routes: [], navigation: [], dependencies: Object.keys(pkg.dependencies || {}), status: ModuleStatus.ACTIVE, priority: 100, author: pkg.author || "Unknown", license: pkg.license || "MIT", repository: pkg.repository?.url }; } /** * Check if package is a module package */ isModulePackage(packageJson) { return packageJson.name && packageJson.name.startsWith("@dbs-portal/module-"); } /** * Extract module ID from package name */ extractModuleId(packageName) { return packageName.replace("@dbs-portal/module-", ""); } /** * Format module name from ID */ formatModuleName(moduleId) { return moduleId.split("-").map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" "); } /** * Infer category from module ID */ inferCategoryFromId(moduleId) { const categoryMap = { "identity": "identity", "permission": "identity", "user": "identity", "auth": "identity", "file": "content", "document": "content", "media": "content", "cms": "content", "blog": "content", "chat": "communication", "notification": "communication", "message": "communication", "setting": "system", "config": "system", "audit": "system", "language": "system", "tenant": "system" }; for (const [key, category] of Object.entries(categoryMap)) { if (moduleId.includes(key)) { return category; } } return "business"; } /** * Read and parse package.json file */ async readPackageJson(packagePath) { const [path, fs] = await Promise.all([getPath(), getFs()]); const packageJsonPath = path.join(packagePath, "package.json"); try { const content = await fs.readFile(packageJsonPath, "utf-8"); return JSON.parse(content); } catch (error) { const platformError = createPlatformError("read-package-json", error, [ `Check that ${packageJsonPath} exists`, "Verify the file is valid JSON", "Ensure proper file system permissions" ]); this.logger.error(`Failed to read package.json at ${packageJsonPath}:`, platformError); throw platformError; } } /** * Remove duplicate modules (prefer first occurrence) */ deduplicateModules(modules) { const seen = /* @__PURE__ */ new Set(); return modules.filter((module) => { if (seen.has(module.id)) { return false; } seen.add(module.id); return true; }); } /** * Validate discovered modules */ validateDiscoveredModules(modules, errors, warnings) { const validModules = []; for (const module of modules) { const validation = validateModuleMetadata(module); if (validation.valid) { validModules.push(module); } else { errors.push({ source: `module-${module.id}`, message: `Validation failed: ${validation.errors.map((e) => e.message).join(", ")}`, details: validation.errors }); } if (validation.warnings) { warnings.push({ source: `module-${module.id}`, message: `Validation warnings: ${validation.warnings.map((w) => w.message).join(", ")}`, details: validation.warnings }); } } return validModules; } } export { ModuleDiscoveryService }; //# sourceMappingURL=ModuleDiscoveryService-cnBM5NoQ.js.map