modurize
Version:
Intelligent CLI tool to scaffold dynamic, context-aware modules for Node.js apps with smart CRUD generation and database integration
252 lines (210 loc) ⢠9.34 kB
JavaScript
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
import { createModule } from '../lib/templates.js';
import { interactiveSetup } from '../lib/interactive.js';
import { loadConfig, createConfigFile, mergeConfigWithArgs } from '../lib/config.js';
import { ProjectAnalyzer } from '../lib/analyzer.js';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
// Version info
const VERSION = '1.2.0';
// Help text
const HELP_TEXT = `
modurize - Generate clean module folders for Node.js projects
USAGE:
modurize <moduleName> [options]
modurize init [options] Create configuration file
modurize analyze Analyze project and show recommendations
OPTIONS:
--class Generate class-based controllers/services
--func Generate function-based files (default)
--typescript Generate TypeScript files (.ts extension)
--tests Include test files
--output <dir> Custom output directory (default: src/modules)
--interactive Use interactive prompts
--dry-run Show what would be generated without creating files
--dynamic Use project analysis for intelligent generation (default)
--static Use static templates (ignore project analysis)
--help, -h Show this help message
--version, -v Show version
EXAMPLES:
modurize auth
modurize user --class
modurize product --typescript --tests
modurize auth --output ./custom/modules
modurize auth --interactive
modurize user --dry-run
modurize init --typescript --tests
modurize analyze
For more information, visit: https://github.com/Code-Gale/modurize
`;
// Get args
const args = process.argv.slice(2);
// Handle help and version
if (args.includes('--help') || args.includes('-h')) {
console.log(HELP_TEXT);
process.exit(0);
}
if (args.includes('--version') || args.includes('-v')) {
console.log(`modurize v${VERSION}`);
process.exit(0);
}
// Handle analyze command
if (args[0] === 'analyze') {
(async () => {
try {
console.log('š Analyzing project...\n');
const analyzer = new ProjectAnalyzer();
const analysis = await analyzer.analyze();
const recommendations = analyzer.getRecommendations();
console.log('š Project Analysis Results:');
console.log(` Framework: ${analysis.framework}`);
console.log(` Database: ${analysis.database || 'none'}`);
console.log(` Code Style: ${analysis.codeStyle.style}`);
console.log(` TypeScript: ${analysis.codeStyle.typescript ? 'Yes' : 'No'}`);
console.log(` Existing Modules: ${analysis.existingModules.length}`);
if (analysis.existingModules.length > 0) {
console.log('\nš Existing Modules:');
analysis.existingModules.forEach(module => {
console.log(` - ${module.name} (${module.files.length} files)`);
});
}
console.log('\nš” Recommendations:');
console.log(` Use TypeScript: ${recommendations.useTypeScript ? 'Yes' : 'No'}`);
console.log(` Use Classes: ${recommendations.useClass ? 'Yes' : 'No'}`);
console.log(` Include Tests: ${recommendations.includeTests ? 'Yes' : 'No'}`);
console.log(` Naming Convention: ${recommendations.namingConvention}`);
console.log(` File Extension: .${recommendations.fileExtension}`);
console.log(` Database Integration: ${recommendations.databaseIntegration ? 'Yes' : 'No'}`);
if (recommendations.databaseIntegration) {
console.log(` Database Type: ${analysis.database}`);
}
console.log('\nš Next Steps:');
console.log(` Run: modurize <moduleName> --${recommendations.useTypeScript ? 'typescript' : 'func'} --${recommendations.useClass ? 'class' : 'func'}${recommendations.includeTests ? ' --tests' : ''}`);
} catch (error) {
console.error('ā Analysis failed:', error.message);
process.exit(1);
}
})();
process.exit(0);
}
// Handle init command
if (args[0] === 'init') {
const initFlags = args.slice(1);
const configOptions = {
useTypeScript: initFlags.includes('--typescript'),
includeTests: initFlags.includes('--tests'),
defaultStyle: initFlags.includes('--class') ? 'class' : 'func'
};
createConfigFile(configOptions);
process.exit(0);
}
// Load configuration
const config = loadConfig();
// Handle interactive mode
if (args.includes('--interactive') || args.length === 0) {
(async () => {
try {
console.log('š Analyzing project for intelligent defaults...');
const analyzer = new ProjectAnalyzer();
const analysis = await analyzer.analyze();
const recommendations = analyzer.getRecommendations();
const interactiveConfig = await interactiveSetup();
const targetDir = path.join(process.cwd(), interactiveConfig.outputDir, interactiveConfig.moduleName);
if (fs.existsSync(targetDir)) {
console.error(`ā Module '${interactiveConfig.moduleName}' already exists at ${targetDir}`);
process.exit(1);
}
const generationType = interactiveConfig.useClass ? 'class' : 'func';
console.log('\nšÆ Using intelligent defaults based on project analysis:');
console.log(` TypeScript: ${recommendations.useTypeScript ? 'Yes' : 'No'}`);
console.log(` Tests: ${recommendations.includeTests ? 'Yes' : 'No'}`);
console.log(` Database: ${analysis.database || 'none'}`);
createModule(interactiveConfig.moduleName, targetDir, generationType, {
useTypeScript: recommendations.useTypeScript,
includeTests: recommendations.includeTests,
analysis: analysis
});
} catch (error) {
console.error('ā Interactive setup failed:', error.message);
process.exit(1);
}
})();
process.exit(0);
}
const flags = args.filter(arg => arg.startsWith('--'));
const moduleArgs = args.filter(arg => !arg.startsWith('--'));
if (!moduleArgs.length) {
console.error("ā Please specify a module name.");
console.error("Use 'modurize --help' for usage information.");
process.exit(1);
}
const moduleName = moduleArgs[0].toLowerCase();
const useClass = flags.includes('--class');
const useFunc = flags.includes('--func');
const useTypeScript = flags.includes('--typescript');
const includeTests = flags.includes('--tests');
const isDryRun = flags.includes('--dry-run');
const useStatic = flags.includes('--static');
const useDynamic = !useStatic; // Default to dynamic
// Merge config with command line args
const finalConfig = mergeConfigWithArgs(config, args);
// Handle custom output directory
let outputDir = finalConfig.defaultOutput;
const outputIndex = flags.findIndex(flag => flag === '--output');
if (outputIndex !== -1 && outputIndex + 1 < flags.length) {
outputDir = flags[outputIndex + 1];
}
const targetDir = path.join(process.cwd(), outputDir, moduleName);
// Validate module name
if (!/^[a-z][a-z0-9-]*$/.test(moduleName)) {
console.error("ā Module name must start with a letter and contain only lowercase letters, numbers, and hyphens.");
process.exit(1);
}
if (fs.existsSync(targetDir)) {
console.error(`ā Module '${moduleName}' already exists at ${targetDir}`);
process.exit(1);
}
// Determine generation type
const generationType = finalConfig.defaultStyle;
// Project analysis for dynamic generation
let analysis = {};
if (useDynamic) {
try {
console.log('š Analyzing project for intelligent generation...');
const analyzer = new ProjectAnalyzer();
analysis = await analyzer.analyze();
console.log(` Detected: ${analysis.framework} framework, ${analysis.database || 'no'} database`);
} catch (error) {
console.warn('ā ļø Project analysis failed, using static templates');
analysis = {};
}
}
if (isDryRun) {
const fileExt = finalConfig.useTypeScript ? 'ts' : 'js';
const testExt = finalConfig.useTypeScript ? 'ts' : 'js';
console.log(`š DRY RUN: Would generate ${generationType}-based module '${moduleName}' at:`);
console.log(` ${targetDir}`);
console.log(` āāā ${moduleName}.controller.${fileExt}`);
console.log(` āāā ${moduleName}.service.${fileExt}`);
console.log(` āāā ${moduleName}.routes.${fileExt}`);
console.log(` āāā ${moduleName}.model.${fileExt}`);
console.log(` āāā ${moduleName}.middleware.${fileExt}`);
console.log(` āāā ${moduleName}.validator.${fileExt}`);
if (finalConfig.includeTests) {
console.log(` āāā ${moduleName}.controller.test.${testExt}`);
console.log(` āāā ${moduleName}.service.test.${testExt}`);
}
if (analysis.database && analysis.database !== 'none') {
console.log(` š Database integration: ${analysis.database}`);
}
process.exit(0);
}
// Create module with options
createModule(moduleName, targetDir, generationType, {
useTypeScript: finalConfig.useTypeScript,
includeTests: finalConfig.includeTests,
analysis: analysis
});