UNPKG

nestjs-reverse-engineering

Version:

A powerful TypeScript/NestJS library for database reverse engineering, entity generation, and CRUD operations

532 lines (531 loc) • 21.3 kB
#!/usr/bin/env node "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; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); const commander_1 = require("commander"); const reverse_engineering_service_new_1 = require("../src/reverse-engineering/reverse-engineering-service-new"); const fs = __importStar(require("fs")); const path = __importStar(require("path")); const program = new commander_1.Command(); // Load configuration from file if exists function loadConfig() { const configPaths = [ 'reverse-engineering.config.js', 'reverse-engineering.config.json', '.reverserc.js', '.reverserc.json' ]; for (const configPath of configPaths) { if (fs.existsSync(configPath)) { console.log(`šŸ“„ Loading configuration from ${configPath}`); try { let config; if (configPath.endsWith('.json')) { config = JSON.parse(fs.readFileSync(configPath, 'utf8')); } else { delete require.cache[require.resolve(path.resolve(configPath))]; config = require(path.resolve(configPath)); } // Ensure database password is a string if (config.database && config.database.password !== undefined) { config.database.password = String(config.database.password); } return config; } catch (error) { console.warn(`āš ļø Failed to load config from ${configPath}:`, error.message); } } } return null; } // Create database configuration from CLI options function createDatabaseConfig(options) { const config = {}; // Only add properties that are actually provided via CLI (not defaults) if (options.dialect && options.dialect !== 'postgres') config.type = options.dialect; if (options.host && options.host !== 'localhost') config.host = options.host; if (options.port) config.port = parseInt(options.port); if (options.username || options.user) config.username = options.username || options.user; if (options.password) config.password = String(options.password); if (options.database) config.database = options.database; if (options.schema) config.schema = options.schema; if (options.ssl) config.ssl = options.ssl; return config; } // Main program configuration program .name('nestjs-reverse-engineering') .description('A powerful reverse engineering and CRUD generation library for NestJS/TypeScript') .version('1.0.0'); // Global options program .option('-h, --host <host>', 'Database host') .option('-p, --port <port>', 'Database port') .option('-u, --username <username>', 'Database username') .option('-w, --password <password>', 'Database password') .option('-d, --database <database>', 'Database name') .option('-s, --schema <schema>', 'Database schema') .option('--dialect <dialect>', 'Database dialect (postgres|mysql)') .option('--ssl', 'Use SSL connection') .option('--config <path>', 'Configuration file path') .option('--output-dir <dir>', 'Base output directory') .option('--entities-dir <dir>', 'Entities directory') .option('--sql-dir <dir>', 'SQL output directory') .option('--data-dir <dir>', 'Data export directory'); // Command: Generate entities program .command('generate:entities') .alias('entities') .description('Generate TypeORM entities from database schema') .option('--include-tables <tables>', 'Comma-separated list of tables to include') .option('--exclude-tables <tables>', 'Comma-separated list of tables to exclude') .option('--no-validation', 'Skip validation decorators') .option('--no-swagger', 'Skip Swagger decorators') .option('--no-index', 'Skip index file generation') .action(async (options, command) => { try { const globalOptions = command.parent.opts(); const fileConfig = loadConfig() || {}; const config = { ...fileConfig, database: { ...fileConfig.database, ...createDatabaseConfig(globalOptions) }, paths: { baseOutput: globalOptions.outputDir || fileConfig.paths?.baseOutput || './src', entities: globalOptions.entitiesDir || fileConfig.paths?.entities || './src/entities', crud: globalOptions.outputDir || fileConfig.paths?.crud || './src', sql: globalOptions.sqlDir || fileConfig.paths?.sql || './sql', dataExport: globalOptions.dataDir || fileConfig.paths?.dataExport || './data' }, features: { entities: true, crud: false, sql: false, dataExport: false, generateIndex: !options.noIndex, ...fileConfig.features }, entities: { includeValidation: !options.noValidation, includeSwagger: !options.noSwagger, useTypeORM: true, generateEnums: true, excludedTables: options.excludeTables ? options.excludeTables.split(',') : [], includedTables: options.includeTables ? options.includeTables.split(',') : undefined, ...fileConfig.entities } }; const service = new reverse_engineering_service_new_1.ReverseEngineeringService(); service.initialize(config); await service.generateEntities(); } catch (error) { console.error('āŒ Entity generation failed:', error.message); process.exit(1); } }); // Command: Generate CRUD program .command('generate:crud') .alias('crud') .description('Generate CRUD operations (controllers, services, repositories)') .option('--framework <framework>', 'Framework to use (nestjs|express|fastify)', 'nestjs') .option('--include-tables <tables>', 'Comma-separated list of tables to include') .option('--exclude-tables <tables>', 'Comma-separated list of tables to exclude') .option('--no-validation', 'Skip validation decorators') .option('--no-swagger', 'Skip Swagger decorators') .option('--no-pagination', 'Skip pagination support') .option('--no-filtering', 'Skip filtering support') .option('--no-sorting', 'Skip sorting support') .option('--no-dto', 'Skip DTO generation') .option('--auth', 'Include authentication guards') .option('--tests', 'Generate test files') .action(async (options, command) => { try { const globalOptions = command.parent.opts(); const fileConfig = loadConfig() || {}; const config = { ...fileConfig, database: { ...fileConfig.database, ...createDatabaseConfig(globalOptions) }, paths: { baseOutput: globalOptions.outputDir || fileConfig.paths?.baseOutput || './src', entities: globalOptions.entitiesDir || fileConfig.paths?.entities || './src/entities', crud: globalOptions.outputDir || fileConfig.paths?.crud || './src', sql: globalOptions.sqlDir || fileConfig.paths?.sql || './sql', dataExport: globalOptions.dataDir || fileConfig.paths?.dataExport || './data' }, features: { entities: false, crud: true, sql: false, dataExport: false, generateIndex: false, ...fileConfig.features }, crud: { framework: options.framework, includeValidation: !options.noValidation, includeSwagger: !options.noSwagger, includePagination: !options.noPagination, includeFiltering: !options.noFiltering, includeSorting: !options.noSorting, includeRelations: false, generateTests: options.tests, authGuards: options.auth, useTypeORM: true, useDTO: !options.noDto, excludedTables: options.excludeTables ? options.excludeTables.split(',') : [], includedTables: options.includeTables ? options.includeTables.split(',') : undefined, ...fileConfig.crud } }; const service = new reverse_engineering_service_new_1.ReverseEngineeringService(); service.initialize(config); await service.generateCrud(); } catch (error) { console.error('āŒ CRUD generation failed:', error.message); process.exit(1); } }); // Command: Generate SQL scripts program .command('generate:sql') .alias('sql') .description('Generate SQL scripts (CREATE TABLE, INSERT statements)') .option('--no-create', 'Skip CREATE TABLE scripts') .option('--no-insert', 'Skip INSERT scripts') .option('--no-format', 'Skip SQL formatting') .action(async (options, command) => { try { const globalOptions = command.parent.opts(); const fileConfig = loadConfig() || {}; const config = { ...fileConfig, database: { ...fileConfig.database, ...createDatabaseConfig(globalOptions) }, paths: { baseOutput: globalOptions.outputDir || fileConfig.paths?.baseOutput || './src', entities: globalOptions.entitiesDir || fileConfig.paths?.entities || './src/entities', crud: globalOptions.outputDir || fileConfig.paths?.crud || './src', sql: globalOptions.sqlDir || fileConfig.paths?.sql || './sql', dataExport: globalOptions.dataDir || fileConfig.paths?.dataExport || './data' }, features: { entities: false, crud: false, sql: true, dataExport: false, generateIndex: false, ...fileConfig.features }, sql: { generateCreateTables: !options.noCreate, generateInserts: !options.noInsert, formatSql: !options.noFormat, includeComments: true, ...fileConfig.sql } }; const service = new reverse_engineering_service_new_1.ReverseEngineeringService(); service.initialize(config); await service.generateSql(); } catch (error) { console.error('āŒ SQL generation failed:', error.message); process.exit(1); } }); // Command: Export data program .command('export:data') .alias('data') .description('Export database data with optional masking') .option('--format <format>', 'Export format (sql|json|csv)', 'sql') .option('--batch-size <size>', 'Batch size for exports', '1000') .option('--max-rows <rows>', 'Maximum rows per table') .option('--mask', 'Enable data masking') .option('--mask-fields <fields>', 'Comma-separated list of fields to mask') .action(async (options, command) => { try { const globalOptions = command.parent.opts(); const fileConfig = loadConfig() || {}; const config = { ...fileConfig, database: { ...fileConfig.database, ...createDatabaseConfig(globalOptions) }, paths: { baseOutput: globalOptions.outputDir || fileConfig.paths?.baseOutput || './src', entities: globalOptions.entitiesDir || fileConfig.paths?.entities || './src/entities', crud: globalOptions.outputDir || fileConfig.paths?.crud || './src', sql: globalOptions.sqlDir || fileConfig.paths?.sql || './sql', dataExport: globalOptions.dataDir || fileConfig.paths?.dataExport || './data' }, features: { entities: false, crud: false, sql: false, dataExport: true, generateIndex: false, ...fileConfig.features }, dataExport: { enableMasking: options.mask, batchSize: parseInt(options.batchSize) || 1000, format: options.format, maskedFields: options.maskFields ? options.maskFields.split(',') : ['password', 'email', 'phone', 'ssn', 'credit_card'], maxRows: options.maxRows ? parseInt(options.maxRows) : undefined, ...fileConfig.dataExport } }; const service = new reverse_engineering_service_new_1.ReverseEngineeringService(); service.initialize(config); await service.exportData(); } catch (error) { console.error('āŒ Data export failed:', error.message); process.exit(1); } }); // Command: Generate all program .command('generate:all') .alias('all') .description('Generate everything (entities, CRUD, SQL, data export)') .option('--skip-entities', 'Skip entity generation') .option('--skip-crud', 'Skip CRUD generation') .option('--skip-sql', 'Skip SQL generation') .option('--skip-data', 'Skip data export') .action(async (options, command) => { try { const globalOptions = command.parent.opts(); const fileConfig = loadConfig() || {}; const config = { ...fileConfig, database: { ...fileConfig.database, ...createDatabaseConfig(globalOptions) }, paths: { baseOutput: globalOptions.outputDir || fileConfig.paths?.baseOutput || './src', entities: globalOptions.entitiesDir || fileConfig.paths?.entities || './src/entities', crud: fileConfig.paths?.crud || globalOptions.outputDir || './src', sql: globalOptions.sqlDir || fileConfig.paths?.sql || './sql', dataExport: globalOptions.dataDir || fileConfig.paths?.dataExport || './data' }, features: { ...fileConfig.features, entities: fileConfig.features?.entities !== false && !options.skipEntities, crud: fileConfig.features?.crud !== false && !options.skipCrud, sql: fileConfig.features?.sql !== false && !options.skipSql, dataExport: fileConfig.features?.dataExport !== false && !options.skipData, generateIndex: fileConfig.features?.generateIndex !== false && !options.skipEntities } }; const service = new reverse_engineering_service_new_1.ReverseEngineeringService(); service.initialize(config); await service.generateAll(); } catch (error) { console.error('āŒ Generation failed:', error.message); process.exit(1); } }); // Command: Test connection program .command('test') .description('Test database connection') .action(async (options, command) => { try { const globalOptions = command.parent.opts(); const fileConfig = loadConfig() || {}; const dbConfig = { ...fileConfig.database, ...createDatabaseConfig(globalOptions) }; const service = new reverse_engineering_service_new_1.ReverseEngineeringService(); const success = await service.testConnection(dbConfig); if (!success) { process.exit(1); } } catch (error) { console.error('āŒ Connection test failed:', error.message); process.exit(1); } }); // Command: List tables program .command('tables') .description('List database tables') .action(async (options, command) => { try { const globalOptions = command.parent.opts(); const fileConfig = loadConfig() || {}; const dbConfig = { ...fileConfig.database, ...createDatabaseConfig(globalOptions) }; const service = new reverse_engineering_service_new_1.ReverseEngineeringService(); const tables = await service.getTables(dbConfig); console.log(`\nšŸ“Š Found ${tables.length} tables:\n`); tables.forEach((table, index) => { console.log(`${index + 1}. ${table.tableName} (${table.columns.length} columns)`); }); } catch (error) { console.error('āŒ Failed to list tables:', error.message); process.exit(1); } }); // Command: Generate config file program .command('init') .description('Generate configuration file') .option('--format <format>', 'Config format (js|json)', 'js') .action(async (options) => { try { const configTemplate = { database: { type: 'postgres', host: 'localhost', port: 5432, username: 'postgres', password: 'password', database: 'your_database', schema: 'public' }, paths: { baseOutput: './src', entities: './src/entities', crud: './src', sql: './sql', dataExport: './data' }, features: { entities: true, crud: true, sql: true, dataExport: false, generateIndex: true }, crud: { framework: 'nestjs', includeValidation: true, includeSwagger: true, includePagination: true, includeFiltering: true, includeSorting: true, includeRelations: false, generateTests: false, authGuards: false, useTypeORM: true, useDTO: true, excludedTables: [], includedTables: undefined }, entities: { useTypeORM: true, includeValidation: true, includeSwagger: true, generateEnums: true, excludedTables: [], includedTables: undefined }, sql: { generateCreateTables: true, generateInserts: true, formatSql: true, includeComments: true }, dataExport: { enableMasking: false, batchSize: 1000, format: 'sql', maskedFields: ['password', 'email', 'phone', 'ssn', 'credit_card'], maxRows: undefined } }; const filename = options.format === 'json' ? 'reverse-engineering.config.json' : 'reverse-engineering.config.js'; let content; if (options.format === 'json') { content = JSON.stringify(configTemplate, null, 2); } else { content = `module.exports = ${JSON.stringify(configTemplate, null, 2)};`; } fs.writeFileSync(filename, content); console.log(`āœ… Configuration file created: ${filename}`); console.log('šŸ“ Edit the configuration file to match your setup'); } catch (error) { console.error('āŒ Failed to create config file:', error.message); process.exit(1); } }); // Handle unknown commands program.on('command:*', () => { console.error('āŒ Invalid command: %s', program.args.join(' ')); console.log('See --help for a list of available commands.'); process.exit(1); }); // Show help if no command is provided if (!process.argv.slice(2).length) { program.outputHelp(); process.exit(0); } program.parse(process.argv);