@diagramers/cli
Version:
Diagramers CLI - Command-line tools for managing Diagramers projects
545 lines • 26.6 kB
JavaScript
;
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