embedia
Version:
Zero-configuration AI chatbot integration CLI - direct file copy with embedded API keys
329 lines (276 loc) • 9.8 kB
JavaScript
const inquirer = require('inquirer');
const chalk = require('chalk');
const fs = require('fs-extra');
const path = require('path');
class ConflictResolver {
async resolveConflicts(conflicts, interactive = true) {
const resolutions = [];
// Group conflicts by type
const grouped = this.groupConflictsByType(conflicts);
console.log(chalk.yellow(`\n⚠️ Found ${conflicts.length} potential conflicts\n`));
for (const [type, typeConflicts] of Object.entries(grouped)) {
const resolver = this.getResolverForType(type);
if (resolver) {
const resolution = await resolver.call(this, typeConflicts, interactive);
resolutions.push(resolution);
}
}
return resolutions;
}
groupConflictsByType(conflicts) {
return conflicts.reduce((grouped, conflict) => {
if (!grouped[conflict.type]) {
grouped[conflict.type] = [];
}
grouped[conflict.type].push(conflict);
return grouped;
}, {});
}
getResolverForType(type) {
const resolvers = {
'api_route_conflict': this.resolveAPIConflicts,
'existing_chat': this.resolveExistingChatConflicts,
'style_conflict': this.resolveStyleConflicts,
'dependency_conflict': this.resolveDependencyConflicts,
'env_var_conflict': this.resolveEnvVarConflicts,
'port_conflict': this.resolvePortConflicts
};
return resolvers[type];
}
async resolveAPIConflicts(conflicts, interactive) {
if (!interactive) {
// Automatic resolution - use alternative endpoint
return {
type: 'api_route',
action: 'use_alternative',
endpoint: '/api/embedia-chat',
message: 'Using alternative API endpoint to avoid conflicts',
resolved: true
};
}
console.log(chalk.yellow('\n⚠️ API Route Conflicts Detected:'));
conflicts.forEach(c => {
console.log(` - ${c.file}`);
});
// Automatically use alternative endpoint to avoid conflicts
console.log(chalk.green('\n✓ Automatically using alternative endpoint: /api/embedia-chat'));
return {
type: 'api_route',
action: 'use_alternative',
endpoint: '/api/embedia-chat',
message: 'Using alternative API endpoint to avoid conflicts',
resolved: true
};
}
async resolveExistingChatConflicts(conflicts, interactive) {
if (!interactive) {
return {
type: 'existing_chat',
action: 'proceed_with_warning',
message: 'Proceeding with installation. Existing chat implementation detected.'
};
}
console.log(chalk.yellow('\n💬 Existing Chat Implementation Detected:'));
const uniqueFiles = [...new Set(conflicts.map(c => c.file))].slice(0, 5);
uniqueFiles.forEach(f => {
console.log(` - ${f}`);
});
if (conflicts.length > 5) {
console.log(` ... and ${conflicts.length - 5} more files`);
}
// Automatically install alongside existing chat
console.log(chalk.green('\n✓ Installing Embedia alongside existing chat implementation'));
return {
type: 'existing_chat',
action: 'install_alongside',
message: 'Embedia will be installed without affecting existing chat',
resolved: true
};
}
async resolveStyleConflicts(conflicts, interactive) {
// Style conflicts are usually warnings
const suggestions = conflicts.map(conflict => ({
file: conflict.file,
issue: conflict.issue,
suggestion: this.getStyleSuggestion(conflict.issue)
}));
if (interactive && conflicts.some(c => c.severity === 'error')) {
console.log(chalk.yellow('\n🎨 Style Conflicts:'));
conflicts.forEach(c => {
const icon = c.severity === 'error' ? '❌' : '⚠️';
console.log(` ${icon} ${c.file}: ${c.issue}`);
});
const { acknowledge } = await inquirer.prompt([
{
type: 'confirm',
name: 'acknowledge',
message: 'Proceed with installation? (You may need to adjust styles later)',
default: true
}
]);
if (!acknowledge) {
throw new Error('Installation aborted by user');
}
}
return {
type: 'style_conflict',
action: 'inform',
suggestions: suggestions,
message: 'Embedia uses scoped styles, but review these potential conflicts',
resolved: true
};
}
async resolveDependencyConflicts(conflicts, interactive) {
const criticalConflicts = conflicts.filter(c => c.severity === 'error');
if (criticalConflicts.length === 0) {
return {
type: 'dependency_conflict',
action: 'none_required',
resolved: true
};
}
console.log(chalk.red('\n📦 Dependency Conflicts:'));
criticalConflicts.forEach(c => {
console.log(` ❌ ${c.dependency}: ${c.currentVersion} (requires ${c.requiredVersion})`);
});
if (!interactive) {
throw new Error('Critical dependency conflicts require manual resolution');
}
const { resolution } = await inquirer.prompt([
{
type: 'list',
name: 'resolution',
message: 'Critical dependency conflicts detected',
choices: [
{ name: 'Update dependencies automatically', value: 'auto_update' },
{ name: 'I\'ll update them manually', value: 'manual' },
{ name: 'Abort installation', value: 'abort' }
]
}
]);
switch (resolution) {
case 'auto_update':
return {
type: 'dependency_conflict',
action: 'auto_update',
updates: criticalConflicts.map(c => ({
package: c.dependency,
version: c.requiredVersion
})),
resolved: true
};
case 'manual':
return {
type: 'dependency_conflict',
action: 'manual',
instructions: this.getDependencyInstructions(criticalConflicts),
resolved: false
};
case 'abort':
throw new Error('Installation aborted by user');
}
}
async resolveEnvVarConflicts(conflicts, interactive) {
// Environment variable conflicts are usually informational
return {
type: 'env_var_conflict',
action: 'use_existing',
message: 'Using existing environment variables',
variables: conflicts.map(c => c.variable),
resolved: true
};
}
async resolvePortConflicts(conflicts, interactive) {
// Port conflicts for dev server
return {
type: 'port_conflict',
action: 'use_alternative_ports',
message: 'Dev server will use alternative ports if defaults are in use',
resolved: true
};
}
getStyleSuggestion(issue) {
const suggestions = {
'Global fixed positioning': 'Review z-index values to ensure chat widget appears on top',
'Body overflow hidden': 'May prevent scrolling when chat is open',
'Very high z-index values': 'Ensure Embedia chat widget z-index (9999) is higher',
'Chat-related class names': 'Consider namespacing existing chat classes to avoid conflicts'
};
return suggestions[issue] || 'Review and test after installation';
}
getManualInstructions(type) {
const instructions = {
'api_route': `
## Manual API Route Setup
1. Create a new API route at a non-conflicting path (e.g., /api/embedia-chat)
2. Copy the handler from components/generated/embedia-chat/api/chat/route.js
3. Update the config.json to point to your new endpoint:
{
"apiEndpoint": "/api/embedia-chat"
}
`,
'existing_chat': `
## Removing Existing Chat Implementation
1. Remove or rename existing chat components
2. Update imports throughout your application
3. Remove related API routes
4. Clean up any global styles
5. Re-run the Embedia installation
`
};
return instructions[type] || 'Please resolve conflicts manually and re-run installation.';
}
getReplacementInstructions(conflicts) {
const files = [...new Set(conflicts.map(c => c.file))];
return `
## Replacing Existing Chat Implementation
### Files to review and potentially remove:
${files.map(f => `- ${f}`).join('\n')}
### Steps:
1. Back up your existing chat implementation
2. Remove or comment out chat components
3. Remove related API endpoints
4. Update any pages/components that import the old chat
5. Re-run: npx embedia init --token=YOUR_TOKEN
### Backup Command:
\`\`\`bash
# Create backup of existing files
${files.map(f => `cp ${f} ${f}.backup`).join(' && ')}
\`\`\`
`;
}
getDependencyInstructions(conflicts) {
return `
## Manual Dependency Update Required
### Required updates:
${conflicts.map(c => `- ${c.dependency}: ${c.currentVersion} → ${c.requiredVersion}`).join('\n')}
### Update commands:
\`\`\`bash
# Using npm
${conflicts.map(c => `npm install ${c.dependency}@^${c.requiredVersion}`).join('\n')}
# Using yarn
${conflicts.map(c => `yarn add ${c.dependency}@^${c.requiredVersion}`).join('\n')}
\`\`\`
### After updating:
1. Test your existing application
2. Re-run: npx embedia init --token=YOUR_TOKEN
`;
}
showDetailedConflicts(conflicts) {
console.log(chalk.cyan('\n📋 Detailed Conflict Report:\n'));
const byFile = conflicts.reduce((acc, c) => {
if (!acc[c.file]) acc[c.file] = [];
acc[c.file].push(c);
return acc;
}, {});
Object.entries(byFile).forEach(([file, fileConflicts]) => {
console.log(chalk.bold(`\n${file}:`));
fileConflicts.forEach(c => {
console.log(` - Type: ${c.indicatorType}`);
console.log(` - Pattern: ${c.pattern}`);
});
});
console.log('');
}
}
module.exports = ConflictResolver;