@deepbrainspace/nx-surrealdb
Version:
NX plugin for SurrealDB migrations with modular architecture
336 lines (333 loc) ⢠15.9 kB
JavaScript
;
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