@dbs-portal/core-module-registry
Version:
Core module registry system for automatic module discovery and registration
383 lines (382 loc) • 13 kB
JavaScript
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-Cix3Fefx.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-Dtr212Ba.js.map