UNPKG

@deepbrainspace/nx-surrealdb

Version:

NX plugin for SurrealDB migrations with modular architecture

336 lines (333 loc) • 15.9 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = runExecutor; const tslib_1 = require("tslib"); const devkit_1 = require("@nx/devkit"); const migration_service_1 = require("../../lib/domain/migration-service"); const module_lock_manager_1 = require("../../lib/domain/module-lock-manager"); const debug_1 = require("../../lib/infrastructure/debug"); const picocolors_1 = tslib_1.__importDefault(require("picocolors")); async function runExecutor(options, context) { // Check for required dependencies (skip in test environment) if (process.env.NODE_ENV !== 'test') { try { await Promise.resolve().then(() => tslib_1.__importStar(require('surrealdb'))); } catch { devkit_1.logger.error(` ${picocolors_1.default.red('āœ–')} Missing dependencies for nx-surrealdb Dependencies should have been installed automatically during setup. If you're seeing this error, try: ${picocolors_1.default.cyan('npm install surrealdb dotenv picocolors')} Or re-run the generator to auto-install dependencies: ${picocolors_1.default.cyan('nx g @deepbrainspace/nx-surrealdb:init --force')} `); return { success: false }; } } // Check for environment variables const requiredEnvVars = ['SURREALDB_URL', 'SURREALDB_ROOT_USER', 'SURREALDB_ROOT_PASS']; const missingEnvVars = requiredEnvVars.filter(v => !process.env[v] && !options[v.replace('SURREALDB_', '').toLowerCase()]); if (missingEnvVars.length > 0) { devkit_1.logger.warn(` ${picocolors_1.default.yellow('⚠')} Missing environment variables: ${missingEnvVars.map(v => ` - ${v}`).join('\n')} Please add these to your .env file or pass them as options. Example .env file: SURREALDB_URL=ws://localhost:8000/rpc SURREALDB_ROOT_USER=root SURREALDB_ROOT_PASS=root SURREALDB_NAMESPACE=development SURREALDB_DATABASE=main `); } const engine = new migration_service_1.MigrationService(context); // Enable debug mode if requested debug_1.Debug.setEnabled(!!options.debug); try { // Initialize migration engine await engine.initialize({ url: options.url || '', user: options.user || '', pass: options.pass || '', namespace: options.namespace, database: options.database, envFile: options.envFile, useTransactions: true, // Not needed for status, but required by interface initPath: options.initPath || 'database', schemaPath: options.schemaPath, force: false, // Not applicable for status configPath: options.configPath, debug: options.debug, }); // Determine target modules and filenames const targetModules = options.module !== undefined && options.module !== '' ? String(options.module) .split(',') .map(m => m.trim()) .filter(m => m.length > 0) : undefined; const targetFilenames = options.filename !== undefined && options.filename !== '' ? String(options.filename) .split(',') .map(f => f.trim()) .filter(f => f.length > 0) : undefined; // Get migration status const status = await engine.getMigrationStatus(targetModules); // Initialize lock manager const config = engine.getConfig(); const lockManager = module_lock_manager_1.ModuleLockManager.createLockManager(config); // If filename filtering is specified, show specific file status instead if (targetFilenames && targetFilenames.length > 0) { const resolvedFilenames = await engine.resolveTargetFilenames(targetFilenames, targetModules, 'up'); const fileStatuses = await engine.getFileStatus(resolvedFilenames); if (options.json) { const fileStatus = { files: fileStatuses.map(fs => ({ filename: fs.filename, moduleId: fs.moduleId, number: fs.number, name: fs.name, direction: fs.direction, status: fs.status, appliedAt: fs.appliedAt, checksum: fs.checksum, dependencies: fs.dependencies, dependents: fs.dependents, })), }; console.log(JSON.stringify(fileStatus, null, 2)); return { success: true }; } // Enhanced filename status display const appliedCount = fileStatuses.filter(fs => fs.status === 'applied').length; const pendingCount = fileStatuses.filter(fs => fs.status === 'pending').length; devkit_1.logger.info(`\nšŸ“„ File Status Summary:`); devkit_1.logger.info(` Files Found: ${fileStatuses.length}`); devkit_1.logger.info(` Applied: ${appliedCount}`); devkit_1.logger.info(` Pending: ${pendingCount}`); devkit_1.logger.info(`\nšŸ“‹ File Details:`); for (const fileStatus of fileStatuses) { const statusIcon = fileStatus.status === 'applied' ? 'āœ…' : 'šŸ”„'; const statusText = fileStatus.status === 'applied' ? 'APPLIED' : 'PENDING'; devkit_1.logger.info(`\n ${statusIcon} ${fileStatus.moduleId}/${fileStatus.filename} [${statusText}]`); devkit_1.logger.info(` Number: ${fileStatus.number}`); devkit_1.logger.info(` Name: ${fileStatus.name}`); devkit_1.logger.info(` Direction: ${fileStatus.direction}`); if (fileStatus.appliedAt) { devkit_1.logger.info(` Applied At: ${fileStatus.appliedAt.toISOString()}`); } if (fileStatus.dependencies.length > 0) { devkit_1.logger.info(` Module Dependencies: ${fileStatus.dependencies.join(', ')}`); } if (fileStatus.dependents.length > 0) { devkit_1.logger.info(` Module Dependents: ${fileStatus.dependents.join(', ')}`); } if (options.detailed && fileStatus.checksum) { devkit_1.logger.info(` Checksum: ${fileStatus.checksum.substring(0, 12)}...`); } } // Show dependency graph for involved modules when detailed flag is used if (options.detailed) { const involvedModules = [...new Set(fileStatuses.map(fs => fs.moduleId))]; const moduleStatuses = status.modules.filter(m => involvedModules.includes(m.moduleId)); showDependencyGraph(moduleStatuses, '🌐 Module Dependency Graph:'); } return { success: true }; } if (options.json) { // Output JSON format const output = { modules: status.modules.map(module => { const lockValidation = lockManager.validateModuleLock(module.moduleId); return { moduleId: module.moduleId, appliedMigrations: module.appliedMigrations, pendingMigrations: module.pendingMigrations, lastApplied: module.lastApplied, dependencies: module.dependencies, dependents: module.dependents, status: module.pendingMigrations > 0 ? 'pending' : 'up-to-date', locked: lockValidation.isLocked ? true : undefined, lockReason: lockValidation.reason, }; }), totalApplied: status.totalApplied, totalPending: status.totalPending, dependencyGraph: status.modules.reduce((graph, module) => { graph[module.moduleId] = module.dependencies; return graph; }, {}), }; console.log(JSON.stringify(output, null, 2)); return { success: true }; } // Human-readable format if (status.modules.length === 0) { devkit_1.logger.info('No migration modules found'); return { success: true }; } // Non-detailed output: Show pending migrations list if (!options.detailed && !options.debug) { if (status.totalPending > 0) { devkit_1.logger.info(`${status.totalPending} migration(s) pending, ${status.totalApplied} applied\n`); // Show which migrations are pending for (const module of status.modules) { if (module.pendingMigrations > 0) { devkit_1.logger.info(`šŸ“¦ ${module.moduleId}: ${module.pendingMigrations} pending`); // Get and show pending migration files for this module const pendingMigrations = await engine.findPendingMigrations([module.moduleId]); const modulePendingFiles = pendingMigrations.filter(m => m.moduleId === module.moduleId); for (const migration of modulePendingFiles) { devkit_1.logger.info(` šŸ”„ ${migration.filename}`); } } } } else { devkit_1.logger.info(`All migrations up to date (${status.totalApplied} applied)`); } return { success: true }; } // Detailed output devkit_1.logger.info(`\nšŸ“ˆ Migration Status Summary`); devkit_1.logger.info(` Total Applied: ${status.totalApplied}`); devkit_1.logger.info(` Total Pending: ${status.totalPending}`); if (status.totalPending > 0) { devkit_1.logger.info(` šŸ”„ ${status.totalPending} migration(s) pending`); } else { devkit_1.logger.info(` āœ… All migrations up to date`); } devkit_1.logger.info(`\nšŸ“‹ Module Details:`); for (const module of status.modules) { const statusIcon = module.pendingMigrations > 0 ? 'šŸ”„' : 'āœ…'; const statusText = module.pendingMigrations > 0 ? 'PENDING' : 'UP-TO-DATE'; const coloredStatus = module.pendingMigrations > 0 ? picocolors_1.default.yellow(`[${statusText}]`) : picocolors_1.default.green(`[${statusText}]`); // Add lock information const lockValidation = lockManager.validateModuleLock(module.moduleId); const lockDisplay = lockValidation.isLocked ? ` ${lockValidation.lockIcon}` : ''; devkit_1.logger.info(`\n ${statusIcon} ${module.moduleId} ${coloredStatus}${lockDisplay}`); devkit_1.logger.info(` Applied: ${module.appliedMigrations} migration(s)`); if (module.pendingMigrations > 0) { devkit_1.logger.info(` Pending: ${module.pendingMigrations} migration(s)`); } if (module.lastApplied) { devkit_1.logger.info(` Last Applied: ${module.lastApplied.toISOString()}`); } // Show lock information if locked if (lockValidation.isLocked) { devkit_1.logger.info(` šŸ”’ Locked: ${lockValidation.reason || 'Module is locked for safety'}`); } // Show dependencies if (module.dependencies.length > 0) { devkit_1.logger.info(` Dependencies: ${module.dependencies.join(', ')}`); } // Show dependents if (module.dependents.length > 0) { devkit_1.logger.info(` Dependents: ${module.dependents.join(', ')}`); } // Show detailed info if requested if (options.detailed) { // Get pending migrations for this module (includes dependencies) const pendingMigrations = await engine.findPendingMigrations([module.moduleId]); if (pendingMigrations.length > 0) { devkit_1.logger.info(` Pending Files:`); for (const migration of pendingMigrations) { // Add dependency label if file is from a different module const dependencyLabel = migration.moduleId !== module.moduleId ? ` (dependency: ${migration.moduleId})` : ''; devkit_1.logger.info(` • ${migration.filename}${dependencyLabel}`); } } } } // Show dependency visualization showDependencyGraph(status.modules); return { success: true }; } catch (error) { devkit_1.logger.error('šŸ’„ Status check failed:'); devkit_1.logger.error(error instanceof Error ? error.message : String(error)); return { success: false }; } finally { await engine.close(); } } function showDependencyGraph(modules, title = '🌐 Dependency Graph:') { devkit_1.logger.info(`\n${title}`); // Build a complete tree structure from the modules const tree = buildDependencyTree(modules); // Display the tree displayDependencyTree(tree, ' '); } function buildDependencyTree(modules) { const moduleMap = new Map(modules.map(m => [m.moduleId, m])); const nodeMap = new Map(); const processedModules = new Set(); // Create nodes for all modules for (const module of modules) { nodeMap.set(module.moduleId, { moduleId: module.moduleId, isRoot: module.dependencies.length === 0, children: [], dependencies: module.dependencies, }); } // Build parent-child relationships for (const module of modules) { const parentNode = nodeMap.get(module.moduleId); if (!parentNode) continue; for (const dependent of module.dependents) { const childNode = nodeMap.get(dependent); if (childNode) { parentNode.children.push(childNode); } } } // Find root nodes (modules with no dependencies within our set) const roots = []; for (const module of modules) { const node = nodeMap.get(module.moduleId); if (!node) continue; // Check if all dependencies are outside our module set const hasInternalDependencies = module.dependencies.some(dep => moduleMap.has(dep)); if (!hasInternalDependencies) { roots.push(node); processedModules.add(module.moduleId); } } // If no roots found (circular dependencies or all modules have deps), // find modules with external dependencies only if (roots.length === 0) { for (const module of modules) { if (processedModules.has(module.moduleId)) continue; const node = nodeMap.get(module.moduleId); if (node) { roots.push(node); } } } return roots; } function displayDependencyTree(nodes, prefix, isChild = false) { for (const node of nodes) { const suffix = node.isRoot ? ' (root)' : ''; if (isChild) { devkit_1.logger.info(`${prefix}└─ ${node.moduleId}${suffix}`); } else { devkit_1.logger.info(`${prefix}${node.moduleId}${suffix}`); } // Display children if (node.children.length > 0) { const childPrefix = isChild ? prefix + ' ' : prefix; displayDependencyTree(node.children, childPrefix, true); } } } //# sourceMappingURL=executor.js.map