embedia
Version:
Zero-configuration AI chatbot integration CLI - direct file copy with embedded API keys
288 lines (240 loc) • 8.28 kB
JavaScript
const { execSync } = require('child_process');
const path = require('path');
const fs = require('fs-extra');
const chalk = require('chalk');
const logger = require('../utils/logger');
/**
* BuildVerifier - Ensures Next.js build process remains intact after integration
* Addresses the critical issue of build failures with "Cannot find module '../server/require-hook'"
*/
class BuildVerifier {
constructor(projectPath) {
this.projectPath = projectPath;
this.buildIssues = [];
}
/**
* Quick verification that doesn't require full build
*/
async quickVerify() {
const verifications = {
nextBinary: await this.verifyNextBinary(),
nodeModulesIntegrity: await this.verifyNodeModulesIntegrity(),
configIntegrity: await this.verifyConfigIntegrity(),
packageJsonValid: await this.verifyPackageJson()
};
const allPassed = Object.values(verifications).every(v => v === true);
if (!allPassed) {
logger.warn(chalk.yellow('\n⚠️ Build verification detected potential issues:'));
this.buildIssues.forEach(issue => {
logger.warn(` - ${issue}`);
});
}
return {
passed: allPassed,
verifications,
issues: this.buildIssues
};
}
/**
* Verify Next.js binary is accessible and not corrupted
*/
async verifyNextBinary() {
try {
const nextBinPath = path.join(this.projectPath, 'node_modules', '.bin', 'next');
// Check if next binary exists
if (!await fs.pathExists(nextBinPath)) {
this.buildIssues.push('Next.js binary not found in node_modules/.bin/');
return false;
}
// Try to run next --version
const version = execSync('npx next --version', {
cwd: this.projectPath,
stdio: 'pipe',
encoding: 'utf8'
}).trim();
logger.info(chalk.gray(` Next.js version: ${version}`));
return true;
} catch (error) {
this.buildIssues.push(`Next.js binary check failed: ${error.message}`);
return false;
}
}
/**
* Verify node_modules structure hasn't been corrupted
*/
async verifyNodeModulesIntegrity() {
try {
const criticalPaths = [
'node_modules/next',
'node_modules/next/dist',
'node_modules/next/dist/server',
'node_modules/react',
'node_modules/react-dom'
];
for (const criticalPath of criticalPaths) {
const fullPath = path.join(this.projectPath, criticalPath);
if (!await fs.pathExists(fullPath)) {
this.buildIssues.push(`Missing critical path: ${criticalPath}`);
return false;
}
}
// Check for the specific file that's failing
const requireHookPath = path.join(
this.projectPath,
'node_modules/next/dist/server/require-hook.js'
);
if (!await fs.pathExists(requireHookPath)) {
this.buildIssues.push('Missing next/dist/server/require-hook.js - Next.js installation may be corrupted');
return false;
}
return true;
} catch (error) {
this.buildIssues.push(`Node modules integrity check failed: ${error.message}`);
return false;
}
}
/**
* Verify Next.js config files haven't been corrupted
*/
async verifyConfigIntegrity() {
try {
// Check for next.config.js or next.config.mjs
const configFiles = ['next.config.js', 'next.config.mjs'];
let configFound = false;
for (const configFile of configFiles) {
const configPath = path.join(this.projectPath, configFile);
if (await fs.pathExists(configPath)) {
configFound = true;
// Read and check if it's valid JavaScript
const content = await fs.readFile(configPath, 'utf8');
// Basic syntax check - ensure it's not corrupted
if (content.includes('�') || content.length === 0) {
this.buildIssues.push(`${configFile} appears to be corrupted`);
return false;
}
}
}
return true;
} catch (error) {
this.buildIssues.push(`Config integrity check failed: ${error.message}`);
return false;
}
}
/**
* Verify package.json scripts are intact
*/
async verifyPackageJson() {
try {
const packageJsonPath = path.join(this.projectPath, 'package.json');
const packageJson = await fs.readJson(packageJsonPath);
// Check for required scripts
if (!packageJson.scripts) {
this.buildIssues.push('No scripts section in package.json');
return false;
}
// For Next.js projects, these scripts should exist
const requiredScripts = ['dev', 'build'];
const missingScripts = requiredScripts.filter(script => !packageJson.scripts[script]);
if (missingScripts.length > 0) {
this.buildIssues.push(`Missing required scripts: ${missingScripts.join(', ')}`);
return false;
}
// Verify build script is correct
if (packageJson.scripts.build && !packageJson.scripts.build.includes('next build')) {
this.buildIssues.push('Build script does not use "next build"');
return false;
}
return true;
} catch (error) {
this.buildIssues.push(`Package.json verification failed: ${error.message}`);
return false;
}
}
/**
* Attempt to fix common build issues
*/
async attemptAutoFix() {
logger.info(chalk.blue('\n🔧 Attempting to auto-fix build issues...'));
const fixes = [];
// Fix 1: Reinstall Next.js if require-hook is missing
if (this.buildIssues.some(issue => issue.includes('require-hook'))) {
try {
logger.info(' Reinstalling Next.js...');
execSync('npm uninstall next && npm install next', {
cwd: this.projectPath,
stdio: 'pipe'
});
fixes.push('Reinstalled Next.js');
} catch (error) {
logger.error(' Failed to reinstall Next.js');
}
}
// Fix 2: Restore package.json scripts
if (this.buildIssues.some(issue => issue.includes('scripts'))) {
try {
const packageJsonPath = path.join(this.projectPath, 'package.json');
const packageJson = await fs.readJson(packageJsonPath);
if (!packageJson.scripts) {
packageJson.scripts = {};
}
// Restore default Next.js scripts
packageJson.scripts = {
...packageJson.scripts,
dev: packageJson.scripts.dev || 'next dev',
build: packageJson.scripts.build || 'next build',
start: packageJson.scripts.start || 'next start'
};
await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });
fixes.push('Restored package.json scripts');
} catch (error) {
logger.error(' Failed to restore scripts');
}
}
return fixes;
}
/**
* Create a backup of critical files before integration
*/
static async createBackup(projectPath) {
const backupDir = path.join(projectPath, '.embedia-backup');
await fs.ensureDir(backupDir);
const filesToBackup = [
'package.json',
'package-lock.json',
'next.config.js',
'next.config.mjs'
];
const backedUp = [];
for (const file of filesToBackup) {
const filePath = path.join(projectPath, file);
if (await fs.pathExists(filePath)) {
const backupPath = path.join(backupDir, file);
await fs.copy(filePath, backupPath);
backedUp.push(file);
}
}
return {
backupDir,
backedUp
};
}
/**
* Restore from backup if build is broken
*/
static async restoreFromBackup(projectPath) {
const backupDir = path.join(projectPath, '.embedia-backup');
if (!await fs.pathExists(backupDir)) {
throw new Error('No backup found');
}
const files = await fs.readdir(backupDir);
for (const file of files) {
const backupPath = path.join(backupDir, file);
const restorePath = path.join(projectPath, file);
await fs.copy(backupPath, restorePath, { overwrite: true });
}
// Clean up backup
await fs.remove(backupDir);
return files;
}
}
module.exports = BuildVerifier;