vibe-seo
Version:
AI-friendly SEO generator for modern web frameworks
246 lines (205 loc) • 9.08 kB
JavaScript
const { Command } = require('commander');
const fs = require('fs-extra');
const path = require('path');
const chalk = require('chalk');
const ora = require('ora');
const { loadConfigWithFrameworkDetection, generateSitemap, generateRobots, generateMetaTags, generateAll, scanPages } = require('../lib');
const program = new Command();
program
.name('vibe-seo-gen')
.description('Generate SEO files for your project')
.version('1.0.0');
program
.command('sitemap')
.description('Generate sitemap.xml')
.option('-c, --config <path>', 'configuration file path', './seo/config/seo.config.yaml')
.option('-o, --output <path>', 'output directory', './seo/output')
.option('--debug', 'enable debug logging')
.action(async (options) => {
const spinner = ora('Generating sitemap...').start();
try {
if (options.debug) {
spinner.stop();
console.log(chalk.yellow('Debug mode enabled'));
console.log(chalk.cyan('Config path:'), options.config);
console.log(chalk.cyan('Output path:'), options.output);
spinner.start();
}
const config = await loadConfigWithFrameworkDetection(options.config, process.cwd());
if (options.debug) {
spinner.stop();
console.log(chalk.cyan('Loaded config:'), JSON.stringify(config, null, 2));
spinner.start();
}
const pages = await scanPages(config);
if (options.debug) {
spinner.stop();
console.log(chalk.cyan('Detected pages:'), pages.length);
pages.forEach(page => console.log(` - ${page.url} (${page.framework})`));
spinner.start();
}
await generateSitemap(pages, config, options.output);
spinner.succeed(chalk.green('Sitemap generated successfully!'));
} catch (error) {
spinner.fail(chalk.red('Failed to generate sitemap'));
console.error(chalk.red('Error details:'), error.message);
if (options.debug) {
console.error(chalk.red('Stack trace:'), error.stack);
}
process.exit(1);
}
});
program
.command('robots')
.description('Generate robots.txt')
.option('-c, --config <path>', 'configuration file path', './seo/config/seo.config.yaml')
.option('-o, --output <path>', 'output directory', './seo/output')
.option('--debug', 'enable debug logging')
.action(async (options) => {
const spinner = ora('Generating robots.txt...').start();
try {
if (options.debug) {
spinner.stop();
console.log(chalk.yellow('Debug mode enabled'));
console.log(chalk.cyan('Config path:'), options.config);
console.log(chalk.cyan('Output path:'), options.output);
// Check if config file exists
const configExists = await fs.pathExists(options.config);
console.log(chalk.cyan('Config file exists:'), configExists);
spinner.start();
}
const config = await loadConfigWithFrameworkDetection(options.config, process.cwd());
if (options.debug) {
spinner.stop();
console.log(chalk.cyan('Site URL:'), config.site?.url);
console.log(chalk.cyan('Site Name:'), config.site?.name);
console.log(chalk.cyan('Bots config:'), config.bots);
spinner.start();
}
await generateRobots(config, options.output);
spinner.succeed(chalk.green('Robots.txt generated successfully!'));
} catch (error) {
spinner.fail(chalk.red('Failed to generate robots.txt'));
console.error(chalk.red('Error details:'), error.message);
if (options.debug) {
console.error(chalk.red('Stack trace:'), error.stack);
}
process.exit(1);
}
});
program
.command('meta')
.description('Generate meta tags for pages')
.option('-c, --config <path>', 'configuration file path', './seo/config/seo.config.yaml')
.option('-o, --output <path>', 'output directory', './seo/output')
.option('--debug', 'enable debug logging')
.action(async (options) => {
const spinner = ora('Generating meta tags...').start();
try {
const config = await loadConfigWithFrameworkDetection(options.config, process.cwd());
const pages = await scanPages(config);
await generateMetaTags(pages, config, options.output);
spinner.succeed(chalk.green('Meta tags generated successfully!'));
} catch (error) {
spinner.fail(chalk.red('Failed to generate meta tags'));
console.error(chalk.red('Error details:'), error.message);
if (options.debug) {
console.error(chalk.red('Stack trace:'), error.stack);
}
process.exit(1);
}
});
program
.command('all')
.description('Generate all SEO files')
.option('-c, --config <path>', 'configuration file path', './seo/config/seo.config.yaml')
.option('-o, --output <path>', 'output directory', './seo/output')
.option('--debug', 'enable debug logging')
.action(async (options) => {
const spinner = ora('Generating all SEO files...').start();
try {
if (options.debug) {
spinner.stop();
console.log(chalk.yellow('Debug mode enabled'));
console.log(chalk.cyan('Config path:'), options.config);
console.log(chalk.cyan('Output path:'), options.output);
console.log(chalk.cyan('Current directory:'), process.cwd());
// Check if config file exists
const configExists = await fs.pathExists(options.config);
console.log(chalk.cyan('Config file exists:'), configExists);
spinner.start();
}
const config = await loadConfigWithFrameworkDetection(options.config, process.cwd());
if (options.debug) {
spinner.stop();
console.log(chalk.cyan('Loaded config successfully'));
console.log(chalk.cyan('Site URL:'), config.site?.url);
console.log(chalk.cyan('Site Name:'), config.site?.name);
console.log(chalk.cyan('Framework:'), config.framework);
spinner.start();
}
spinner.text = 'Scanning pages...';
const pages = await scanPages(config);
if (options.debug) {
spinner.stop();
console.log(chalk.cyan('Pages detected:'), pages.length);
pages.forEach(page => console.log(` - ${page.url} (${page.framework || page.type})`));
spinner.start();
}
spinner.text = 'Generating all SEO files...';
const results = await generateAll(pages, config, options.output);
spinner.succeed(chalk.green('All SEO files generated successfully!'));
console.log('\nGenerated files:');
console.log(chalk.cyan('• sitemap.xml'));
console.log(chalk.cyan('• robots.txt'));
console.log(chalk.cyan('• meta-tags/ (directory)'));
console.log(chalk.cyan('• jsonld/ (directory)'));
if (results.fixes && results.fixes.length > 0) {
console.log(chalk.green('\n🔧 Automatic compatibility fixes applied:'));
results.fixes.forEach(fix => console.log(chalk.cyan(` • ${fix}`)));
console.log(chalk.green('\n✅ Your Vite React app is now inherently compatible!'));
console.log(chalk.gray(' No manual configuration needed - everything works out of the box.'));
}
if (results.deployment && results.deployment.length > 0) {
console.log(chalk.yellow('\n📦 Deployment guides generated for reference:'));
console.log(chalk.cyan('• VITE_REACT_DEPLOYMENT_GUIDE.md'));
console.log(chalk.cyan('• vite.config.seo.ts'));
console.log(chalk.cyan('• vercel.json'));
console.log(chalk.cyan('• netlify.toml'));
console.log(chalk.gray('\n These are for reference only - fixes are already applied automatically.'));
}
} catch (error) {
spinner.fail(chalk.red('Failed to generate SEO files'));
console.error(chalk.red('Error details:'), error.message);
if (options.debug) {
console.error(chalk.red('Stack trace:'), error.stack);
}
process.exit(1);
}
});
program
.command('scan')
.description('Scan and list detected pages')
.option('-c, --config <path>', 'configuration file path', './seo/config/seo.config.yaml')
.option('--debug', 'enable debug logging')
.action(async (options) => {
const spinner = ora('Scanning pages...').start();
try {
const config = await loadConfigWithFrameworkDetection(options.config, process.cwd());
const pages = await scanPages(config);
spinner.succeed(chalk.green(`Found ${pages.length} pages`));
console.log('\nDetected pages:');
pages.forEach(page => {
console.log(chalk.cyan(`• ${page.url}`) + chalk.gray(` (${page.type})`));
});
} catch (error) {
spinner.fail(chalk.red('Failed to scan pages'));
console.error(chalk.red('Error details:'), error.message);
if (options.debug) {
console.error(chalk.red('Stack trace:'), error.stack);
}
process.exit(1);
}
});
program.parse();