UNPKG

@diagramers/cli

Version:

Diagramers CLI - Command-line tools for managing Diagramers projects

545 lines 26.6 kB
"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; }; })(); var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.TemplateUpdater = void 0; const fs = __importStar(require("fs-extra")); const path = __importStar(require("path")); const glob = __importStar(require("glob")); const chalk_1 = __importDefault(require("chalk")); const child_process_1 = require("child_process"); class TemplateUpdater { constructor() { this.currentProjectPath = process.cwd(); this.tempTemplatePath = path.join(this.currentProjectPath, '.temp-template'); } async update(options) { // Check if we're in a valid project directory if (!await this.isValidProject()) { throw new Error('Not a valid diagramers API project. Please run this command from your project root.'); } console.log(chalk_1.default.blue('🔄 Checking for template updates...')); // Show current project version const currentVersion = await this.getCurrentProjectVersion(); console.log(chalk_1.default.blue(`📦 Current project version: ${currentVersion}`)); // Create backup if requested if (options.backup) { console.log(chalk_1.default.blue('💾 Creating backup...')); await this.createBackup(); } try { // Download latest template const templatePackage = options.template || '@diagramers/api'; console.log(chalk_1.default.blue(`📥 Downloading template: ${templatePackage}`)); await this.downloadTemplate(templatePackage, options.version); // Get list of files that can be updated const updateableFiles = await this.getUpdateableFiles(); console.log(chalk_1.default.blue(`📋 Found ${updateableFiles.length} files to update`)); // Check for conflicts const conflicts = await this.checkConflicts(updateableFiles); if (conflicts.length > 0 && !options.force) { console.log(chalk_1.default.yellow('⚠️ Conflicts detected in the following files:')); conflicts.forEach(file => console.log(chalk_1.default.yellow(` - ${file}`))); console.log(chalk_1.default.yellow('Use --force to overwrite these files')); throw new Error('Update aborted due to conflicts'); } // Get template version for display const templateVersion = await this.getTemplateVersion(); // Perform the update console.log(chalk_1.default.blue('🔄 Performing update...')); await this.performUpdate(updateableFiles, options.force); console.log(chalk_1.default.green('✅ Project updated successfully!')); console.log(chalk_1.default.blue(`📦 Template version: ${templateVersion}`)); console.log(chalk_1.default.yellow('📝 Review the changes and test your application')); console.log(chalk_1.default.yellow('💡 Run "npm install" to update dependencies if needed')); } finally { // Clean up temporary template await this.cleanup(); } } async checkForUpdates(options) { // Check if we're in a valid project directory if (!await this.isValidProject()) { throw new Error('Not a valid diagramers API project. Please run this command from your project root.'); } console.log(chalk_1.default.blue('🔍 Checking for available updates...')); // Show current project version const currentVersion = await this.getCurrentProjectVersion(); console.log(chalk_1.default.blue(`📦 Current project version: ${currentVersion}`)); try { // Download latest template const templatePackage = options.template || '@diagramers/api'; console.log(chalk_1.default.blue(`📥 Checking template: ${templatePackage}`)); await this.downloadTemplate(templatePackage, options.version); // Get list of files that can be updated const updateableFiles = await this.getUpdateableFiles(); console.log(chalk_1.default.blue(`📋 Found ${updateableFiles.length} files that can be updated`)); // Check for conflicts const conflicts = await this.checkConflicts(updateableFiles); if (conflicts.length > 0) { console.log(chalk_1.default.yellow('⚠️ Conflicts detected in the following files:')); conflicts.forEach(file => console.log(chalk_1.default.yellow(` - ${file}`))); console.log(chalk_1.default.yellow('Use --force to override these files')); } else { console.log(chalk_1.default.green('✅ No conflicts detected')); } // Get template version for display const templateVersion = await this.getTemplateVersion(); console.log(chalk_1.default.blue(`📦 Available template version: ${templateVersion}`)); if (templateVersion !== currentVersion) { console.log(chalk_1.default.green('🔄 Update available!')); console.log(chalk_1.default.yellow('💡 Run without --check-only to perform the update')); } else { console.log(chalk_1.default.green('✅ Project is up to date')); } } finally { // Clean up temporary template await this.cleanup(); } } async dryRunUpdate(options) { // Check if we're in a valid project directory if (!await this.isValidProject()) { throw new Error('Not a valid diagramers API project. Please run this command from your project root.'); } console.log(chalk_1.default.blue('🔍 Performing dry run update...')); // Show current project version const currentVersion = await this.getCurrentProjectVersion(); console.log(chalk_1.default.blue(`📦 Current project version: ${currentVersion}`)); try { // Download latest template const templatePackage = options.template || '@diagramers/api'; console.log(chalk_1.default.blue(`📥 Checking template: ${templatePackage}`)); await this.downloadTemplate(templatePackage, options.version); // Get list of files that can be updated const updateableFiles = await this.getUpdateableFiles(); console.log(chalk_1.default.blue(`📋 Found ${updateableFiles.length} files that can be updated`)); // Check for conflicts const conflicts = await this.checkConflicts(updateableFiles); if (conflicts.length > 0) { console.log(chalk_1.default.yellow('⚠️ Conflicts detected in the following files:')); conflicts.forEach(file => console.log(chalk_1.default.yellow(` - ${file}`))); console.log(chalk_1.default.yellow('Use --force to override these files')); } else { console.log(chalk_1.default.green('✅ No conflicts detected')); } // Get template version for display const templateVersion = await this.getTemplateVersion(); console.log(chalk_1.default.blue(`📦 Available template version: ${templateVersion}`)); // Show what would be updated console.log(chalk_1.default.blue('📝 Files that would be updated:')); updateableFiles.forEach(file => { const status = conflicts.includes(file) ? chalk_1.default.yellow('⚠️ CONFLICT') : chalk_1.default.green('✅ UPDATE'); console.log(` ${status} ${file}`); }); console.log(chalk_1.default.green('✅ Dry run completed!')); console.log(chalk_1.default.yellow('💡 Run without --dry-run to perform the actual update')); } finally { // Clean up temporary template await this.cleanup(); } } async isValidProject() { const packageJsonPath = path.join(this.currentProjectPath, 'package.json'); const mainTsPath = path.join(this.currentProjectPath, 'src', 'main.ts'); const modulesPath = path.join(this.currentProjectPath, 'src', 'modules'); // Check for required files and directories const hasPackageJson = await fs.pathExists(packageJsonPath); const hasMainTs = await fs.pathExists(mainTsPath); const hasModules = await fs.pathExists(modulesPath); if (!hasPackageJson) { throw new Error('package.json not found. This does not appear to be a Node.js project.'); } if (!hasMainTs) { throw new Error('src/main.ts not found. This does not appear to be a diagramers API project.'); } if (!hasModules) { throw new Error('src/modules directory not found. This does not appear to be a diagramers API project.'); } return true; } async createBackup() { const timestamp = new Date().toISOString().replace(/[:.]/g, '-'); const backupPath = path.join(this.currentProjectPath, `backup-${timestamp}`); // Create backup directory first await fs.ensureDir(backupPath); // Copy files individually to avoid the subdirectory issue const filesToBackup = [ 'src', 'config', 'scripts', 'main.ts', 'package.json', 'tsconfig.json', 'webpack.config.js', 'README.md', 'DEVELOPER_GUIDE.md', '.env.example', '.gitignore' ]; for (const item of filesToBackup) { const sourcePath = path.join(this.currentProjectPath, item); const destPath = path.join(backupPath, item); if (await fs.pathExists(sourcePath)) { await fs.copy(sourcePath, destPath); } } console.log(chalk_1.default.blue(`📦 Backup created at: ${backupPath}`)); } async downloadTemplate(templatePackage, targetVersion) { const packageWithVersion = targetVersion && targetVersion !== 'latest' ? `${templatePackage}@${targetVersion}` : templatePackage; console.log(chalk_1.default.blue(`📦 Downloading latest template: ${packageWithVersion}`)); // Create temporary directory await fs.ensureDir(this.tempTemplatePath); // Only download from npm registry await this.downloadFromNpm(packageWithVersion); } async downloadFromNpm(templatePackage) { const npmCachePath = path.join(this.tempTemplatePath, 'node_modules', templatePackage); try { // Use npm pack to download the package (0, child_process_1.execSync)(`npm pack ${templatePackage} --silent`, { cwd: this.tempTemplatePath, stdio: 'pipe' }); // Find the downloaded tarball const files = await fs.readdir(this.tempTemplatePath); const tarball = files.find(file => file.endsWith('.tgz')); if (!tarball) { throw new Error('No tarball found after npm pack'); } // Extract the tarball (0, child_process_1.execSync)(`tar -xzf ${tarball} --strip-components=1`, { cwd: this.tempTemplatePath, stdio: 'pipe' }); // Clean up tarball await fs.remove(path.join(this.tempTemplatePath, tarball)); console.log(chalk_1.default.green(`✅ Template downloaded from npm: ${templatePackage}`)); } catch (error) { throw new Error(`Failed to download template from npm: ${error.message}`); } } async getUpdateableFiles() { const updateablePatterns = [ 'src/helpers/**/*', 'src/config/**/*', 'src/server/**/*', 'src/core/**/*', 'src/shared/**/*', 'src/plugins/**/*', 'src/modules/**/*', // Include all module files 'config/**/*', 'scripts/**/*', 'webpack.config.js', 'tsconfig.json', '.env.example', 'package.json', 'README.md', 'DEVELOPER_GUIDE.md' ]; const files = []; for (const pattern of updateablePatterns) { const matches = glob.sync(pattern, { cwd: this.tempTemplatePath }); // Filter out directories and only keep files for (const match of matches) { const fullPath = path.join(this.tempTemplatePath, match); const stats = await fs.stat(fullPath); if (stats.isFile()) { files.push(match); } } } return files; } async checkConflicts(files) { const conflicts = []; for (const file of files) { const templatePath = path.join(this.tempTemplatePath, file); const projectPath = path.join(this.currentProjectPath, file); try { // Check if both files exist and are actually files (not directories) const templateExists = await fs.pathExists(templatePath); const projectExists = await fs.pathExists(projectPath); if (templateExists && projectExists) { const templateStats = await fs.stat(templatePath); const projectStats = await fs.stat(projectPath); // Only compare if both are files if (templateStats.isFile() && projectStats.isFile()) { const templateContent = await fs.readFile(templatePath, 'utf8'); const projectContent = await fs.readFile(projectPath, 'utf8'); // Simple conflict detection - if files are different if (templateContent !== projectContent) { conflicts.push(file); } } } } catch (error) { // If there's an error reading the file, skip it console.log(chalk_1.default.yellow(`⚠️ Warning: Could not check conflicts for ${file}: ${error.message}`)); } } return conflicts; } async performUpdate(files, force = false) { console.log(chalk_1.default.blue('📝 Updating project files...')); for (const file of files) { const templatePath = path.join(this.tempTemplatePath, file); const projectPath = path.join(this.currentProjectPath, file); try { if (await fs.pathExists(templatePath)) { const stats = await fs.stat(templatePath); // Only process files, not directories if (stats.isFile()) { // Special handling for package.json to preserve project-specific settings if (file === 'package.json') { await this.updatePackageJson(templatePath, projectPath); } else if (file.startsWith('src/modules/') && !force) { // For module files, use intelligent update to preserve customizations await this.intelligentlyUpdateModuleFile(templatePath, projectPath, file); } else { await fs.ensureDir(path.dirname(projectPath)); await fs.copy(templatePath, projectPath); console.log(chalk_1.default.green(`✅ Updated: ${file}`)); } } } } catch (error) { console.log(chalk_1.default.yellow(`⚠️ Warning: Could not update ${file}: ${error.message}`)); } } } async updatePackageJson(templatePath, projectPath) { try { const templatePackageJson = JSON.parse(await fs.readFile(templatePath, 'utf8')); const projectPackageJson = JSON.parse(await fs.readFile(projectPath, 'utf8')); // Preserve project-specific fields const preservedFields = [ 'name', 'version', 'description', 'repository', 'bugs', 'homepage', 'author', 'license' ]; // Merge dependencies and devDependencies const mergedPackageJson = { ...templatePackageJson, ...projectPackageJson, dependencies: { ...templatePackageJson.dependencies, ...projectPackageJson.dependencies }, devDependencies: { ...templatePackageJson.devDependencies, ...projectPackageJson.devDependencies } }; // Restore preserved fields for (const field of preservedFields) { if (projectPackageJson[field]) { mergedPackageJson[field] = projectPackageJson[field]; } } await fs.writeFile(projectPath, JSON.stringify(mergedPackageJson, null, 2)); console.log(chalk_1.default.green('✅ Updated: package.json (preserved project settings)')); } catch (error) { console.log(chalk_1.default.yellow(`⚠️ Warning: Could not update package.json: ${error.message}`)); } } async intelligentlyUpdateModuleFile(templatePath, projectPath, filePath) { try { // Check if project file exists if (!await fs.pathExists(projectPath)) { // If project file doesn't exist, copy from template await fs.ensureDir(path.dirname(projectPath)); await fs.copy(templatePath, projectPath); console.log(chalk_1.default.green(`✅ Created: ${filePath}`)); return; } const templateContent = await fs.readFile(templatePath, 'utf8'); const projectContent = await fs.readFile(projectPath, 'utf8'); // For different file types, use different update strategies if (filePath.includes('.entity.ts')) { await this.updateEntityFile(templateContent, projectContent, projectPath, filePath); } else if (filePath.includes('.schema.ts')) { await this.updateSchemaFile(templateContent, projectContent, projectPath, filePath); } else if (filePath.includes('.service.ts')) { await this.updateServiceFile(templateContent, projectContent, projectPath, filePath); } else if (filePath.includes('.controller.ts')) { await this.updateControllerFile(templateContent, projectContent, projectPath, filePath); } else if (filePath.includes('.routes.ts')) { await this.updateRoutesFile(templateContent, projectContent, projectPath, filePath); } else { // For other module files, check if they're significantly different if (templateContent !== projectContent) { console.log(chalk_1.default.yellow(`⚠️ Module file has customizations: ${filePath}`)); console.log(chalk_1.default.yellow(` Use --force to overwrite or manually merge changes`)); } else { console.log(chalk_1.default.gray(` No changes needed: ${filePath}`)); } } } catch (error) { console.log(chalk_1.default.yellow(`⚠️ Warning: Could not intelligently update ${filePath}: ${error.message}`)); } } async updateEntityFile(templateContent, projectContent, projectPath, filePath) { // For entity files, preserve custom fields and interfaces const hasCustomFields = projectContent.includes('// Custom fields') || projectContent.includes('// TODO: Add custom fields'); if (hasCustomFields) { console.log(chalk_1.default.yellow(`⚠️ Entity file has custom fields: ${filePath}`)); console.log(chalk_1.default.yellow(` Preserving customizations - use --force to overwrite`)); } else { await fs.writeFile(projectPath, templateContent); console.log(chalk_1.default.green(`✅ Updated: ${filePath}`)); } } async updateSchemaFile(templateContent, projectContent, projectPath, filePath) { // For schema files, preserve custom fields and indexes const hasCustomFields = projectContent.includes('// Custom fields') || projectContent.includes('// TODO: Add custom fields'); if (hasCustomFields) { console.log(chalk_1.default.yellow(`⚠️ Schema file has custom fields: ${filePath}`)); console.log(chalk_1.default.yellow(` Preserving customizations - use --force to overwrite`)); } else { await fs.writeFile(projectPath, templateContent); console.log(chalk_1.default.green(`✅ Updated: ${filePath}`)); } } async updateServiceFile(templateContent, projectContent, projectPath, filePath) { // For service files, preserve custom methods and business logic const hasCustomMethods = projectContent.includes('// TODO: Implement') || projectContent.includes('// Custom method') || projectContent.includes('async custom'); if (hasCustomMethods) { console.log(chalk_1.default.yellow(`⚠️ Service file has custom methods: ${filePath}`)); console.log(chalk_1.default.yellow(` Preserving customizations - use --force to overwrite`)); } else { await fs.writeFile(projectPath, templateContent); console.log(chalk_1.default.green(`✅ Updated: ${filePath}`)); } } async updateControllerFile(templateContent, projectContent, projectPath, filePath) { // For controller files, preserve custom endpoints and business logic const hasCustomEndpoints = projectContent.includes('// TODO: Implement') || projectContent.includes('// Custom endpoint') || projectContent.includes('async custom'); if (hasCustomEndpoints) { console.log(chalk_1.default.yellow(`⚠️ Controller file has custom endpoints: ${filePath}`)); console.log(chalk_1.default.yellow(` Preserving customizations - use --force to overwrite`)); } else { await fs.writeFile(projectPath, templateContent); console.log(chalk_1.default.green(`✅ Updated: ${filePath}`)); } } async updateRoutesFile(templateContent, projectContent, projectPath, filePath) { // For routes files, preserve custom route definitions const hasCustomRoutes = projectContent.includes('// Custom routes') || projectContent.includes('// TODO: Add custom routes') || projectContent.includes('router.get(\'/custom') || projectContent.includes('router.post(\'/custom'); if (hasCustomRoutes) { console.log(chalk_1.default.yellow(`⚠️ Routes file has custom routes: ${filePath}`)); console.log(chalk_1.default.yellow(` Preserving customizations - use --force to overwrite`)); } else { await fs.writeFile(projectPath, templateContent); console.log(chalk_1.default.green(`✅ Updated: ${filePath}`)); } } async getCurrentProjectVersion() { try { const packageJsonPath = path.join(this.currentProjectPath, 'package.json'); if (await fs.pathExists(packageJsonPath)) { const packageJson = JSON.parse(await fs.readFile(packageJsonPath, 'utf8')); return packageJson.version || 'unknown'; } } catch (error) { // Ignore errors } return 'unknown'; } async getTemplateVersion() { try { const packageJsonPath = path.join(this.tempTemplatePath, 'package.json'); if (await fs.pathExists(packageJsonPath)) { const packageJson = JSON.parse(await fs.readFile(packageJsonPath, 'utf8')); return packageJson.version || 'unknown'; } } catch (error) { // Ignore errors } return 'unknown'; } async cleanup() { if (await fs.pathExists(this.tempTemplatePath)) { await fs.remove(this.tempTemplatePath); } } } exports.TemplateUpdater = TemplateUpdater; //# sourceMappingURL=template-updater.js.map