@casoon/auditmysite
Version:
Professional website analysis suite with robust accessibility testing, Core Web Vitals performance monitoring, SEO analysis, and content optimization insights. Features isolated browser contexts, retry mechanisms, and comprehensive API endpoints for profe
521 lines (518 loc) β’ 24.7 kB
JavaScript
;
/**
* π§ Audit Command
*
* Main command for running accessibility audits.
* Replaces the monolithic CLI logic with clean command structure.
*/
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.AuditCommand = void 0;
const base_command_1 = require("./base-command");
const standard_pipeline_1 = require("../../core/pipeline/standard-pipeline");
const sitemap_discovery_1 = require("../../core/parsers/sitemap-discovery");
const web_vitals_collector_1 = require("../../core/performance/web-vitals-collector");
const package_json_1 = __importDefault(require("../../../package.json"));
const inquirer_1 = __importDefault(require("inquirer"));
const path = __importStar(require("path"));
const fs = __importStar(require("fs"));
class AuditCommand extends base_command_1.BaseCommand {
constructor() {
super('audit', 'Run accessibility audit on sitemap URLs');
}
validate(args) {
const errors = [];
// Validate sitemap URL
const urlValidation = this.validateSitemapUrl(args.sitemapUrl);
if (!urlValidation.valid) {
errors.push(`Invalid sitemap URL: ${urlValidation.error}`);
}
// Validate maxPages if provided
if (args.maxPages !== undefined) {
if (args.maxPages < 1 || args.maxPages > 1000) {
errors.push('maxPages must be between 1 and 1000');
}
}
// Validate formats
if (args.format && args.format.length > 0) {
const validFormats = ['html', 'markdown', 'json', 'csv'];
const invalidFormats = args.format.filter(f => !validFormats.includes(f));
if (invalidFormats.length > 0) {
errors.push(`Invalid formats: ${invalidFormats.join(', ')}. Valid formats: ${validFormats.join(', ')}`);
}
}
// Validate budget template
if (args.budget && !['default', 'ecommerce', 'corporate', 'blog'].includes(args.budget)) {
errors.push('budget must be one of: default, ecommerce, corporate, blog');
}
return {
valid: errors.length === 0,
errors
};
}
async execute(args) {
try {
// Validate arguments
const validation = this.validate(args);
if (!validation.valid) {
return this.error(`Validation failed: ${validation.errors.join(', ')}`);
}
// Show header
this.logProgress(`AuditMySite v${package_json_1.default.version} - Professional Accessibility Testing`);
this.logProgress(`Sitemap: ${args.sitemapUrl}`);
// Determine configuration
const config = await this.buildConfiguration(args);
// Show configuration summary
this.showConfigurationSummary(config, args);
// Discover sitemap if needed
const finalSitemapUrl = await this.discoverSitemap(args.sitemapUrl);
// Create output directory
const outputInfo = this.setupOutputDirectory(finalSitemapUrl, args.outputDir);
// Run the audit
const result = await this.runAudit(finalSitemapUrl, config, outputInfo);
// Show results
this.showResults(result);
return this.success('Audit completed successfully', result);
}
catch (error) {
const errorMessage = this.formatError(error);
this.logError(`Audit failed: ${errorMessage}`);
if (args.verbose) {
console.error('Full error details:', error);
}
else {
this.logProgress('Run with --verbose for detailed error information');
}
return this.error(errorMessage);
}
}
async buildConfiguration(args) {
// Smart defaults - sitemapUrl will be set later in runAudit
const baseConfig = {
sitemapUrl: args.sitemapUrl, // Required property
maxPages: args.maxPages || (args.full ? 1000 : 5),
timeout: 10000,
pa11yStandard: 'WCAG2AA',
outputDir: args.outputDir || './reports',
outputFormat: Array.isArray(args.format) ?
(args.format[0] === 'json' ? 'html' :
args.format[0] === 'markdown' ? 'markdown' : 'html') :
(args.format === 'json' ? 'html' :
args.format === 'markdown' ? 'markdown' : 'html'),
maxConcurrent: 2,
generateDetailedReport: true,
generatePerformanceReport: true,
// generateSeoReport: false, // Not in StandardPipelineOptions
// generateSecurityReport: false, // Not in StandardPipelineOptions
// usePa11y: true, // Not in StandardPipelineOptions
collectPerformanceMetrics: true,
// Queue system is now the default
// π NEW: Enhanced analysis enabled (BrowserPoolManager fixed)
useEnhancedAnalysis: true,
contentWeightAnalysis: true,
enhancedPerformanceAnalysis: true,
enhancedSeoAnalysis: true
};
// Store all formats for unified report system
if (Array.isArray(args.format) && args.format.length > 0) {
baseConfig.outputFormats = args.format;
}
// Analysis configuration from CLI args - disable specific features
if (args.noPerformance) {
baseConfig.enhancedPerformanceAnalysis = false;
}
if (args.noSeo) {
baseConfig.enhancedSeoAnalysis = false;
}
if (args.noContentWeight) {
baseConfig.contentWeightAnalysis = false;
}
// Expert mode - interactive configuration
if (args.expert && !args.nonInteractive) {
return await this.runExpertMode(baseConfig);
}
// Build performance budget
// Performance budget will be handled by the unified report system
return baseConfig;
}
async runExpertMode(baseConfig) {
this.logProgress('Expert Mode - Custom Configuration');
console.log('β'.repeat(50));
const answers = await inquirer_1.default.prompt([
{
type: 'list',
name: 'maxPages',
message: 'π’ How many pages to test?',
choices: [
{ name: 'β‘ 5 pages (Quick test) - ~2 minutes', value: 5 },
{ name: 'π― 20 pages (Standard test) - ~8 minutes', value: 20 },
{ name: 'π 50 pages (Comprehensive) - ~20 minutes', value: 50 },
{ name: 'π All pages (Maximum coverage) - varies', value: 1000 }
],
default: baseConfig.maxPages
},
{
type: 'list',
name: 'standard',
message: 'βΏ Accessibility standard?',
choices: [
{ name: 'π― WCAG 2.1 AA (Recommended) - Industry standard', value: 'WCAG2AA' },
{ name: 'β WCAG 2.1 AAA (Strict) - Highest compliance', value: 'WCAG2AAA' },
{ name: 'πΊπΈ Section 508 (US Federal) - Government sites', value: 'Section508' }
],
default: baseConfig.pa11yStandard
},
{
type: 'checkbox',
name: 'formats',
message: 'π Report formats? (select multiple)',
choices: [
{ name: 'π HTML - Professional reports for stakeholders', value: 'html', checked: true },
{ name: 'π Markdown - Developer-friendly, version control', value: 'markdown' },
{ name: 'π JSON - Machine-readable for CI/CD pipelines', value: 'json' },
{ name: 'π CSV - Data analysis and spreadsheet integration', value: 'csv' }
],
validate: (answer) => {
if (answer.length === 0) {
return 'Please select at least one format';
}
return true;
}
},
{
type: 'confirm',
name: 'enhanced',
message: 'π Enable Enhanced Analysis? (Performance, SEO, Content Weight)',
default: false
},
{
type: 'checkbox',
name: 'enhancedComponents',
message: 'π Which enhanced components? (select multiple)',
choices: [
{ name: 'β‘ Enhanced Performance - Core Web Vitals, advanced metrics', value: 'performance' },
{ name: 'π Enhanced SEO - Meta tags, content quality, readability', value: 'seo' },
{ name: 'π Content Weight - Resource analysis, text-to-code ratio', value: 'contentWeight' }
],
when: (answers) => answers.enhanced,
validate: (answer) => {
if (answer.length === 0) {
return 'Please select at least one enhanced component';
}
return true;
}
},
{
type: 'confirm',
name: 'useUnifiedQueue',
message: 'π§ Use the NEW Unified Queue System? (Recommended)',
default: true
},
{
type: 'number',
name: 'maxConcurrent',
message: 'π Concurrent page tests (1-5)?',
default: baseConfig.maxConcurrent,
validate: (value) => {
const num = parseInt(value?.toString() || '0');
if (num >= 1 && num <= 5)
return true;
return 'Please enter a number between 1 and 5';
}
}
]);
// Store additional formats in extended config for the unified system
const result = {
...baseConfig,
maxPages: answers.maxPages,
pa11yStandard: answers.standard,
outputFormat: (answers.formats[0] === 'markdown' ? 'markdown' : 'html'),
// Queue system is default
maxConcurrent: answers.maxConcurrent
};
// Store all formats for unified system (not in StandardPipelineOptions interface)
result.outputFormats = answers.formats;
// Store enhanced analysis settings
if (answers.enhanced && answers.enhancedComponents) {
result.enhanced = true;
result.enhancedPerformance = answers.enhancedComponents.includes('performance');
result.enhancedSeo = answers.enhancedComponents.includes('seo');
result.contentWeight = answers.enhancedComponents.includes('contentWeight');
}
return result;
}
buildPerformanceBudget(args) {
// Custom budget from CLI options
if (args.lcpBudget || args.clsBudget || args.fcpBudget || args.inpBudget || args.ttfbBudget) {
const defaultBudget = web_vitals_collector_1.BUDGET_TEMPLATES[args.budget || 'default'];
return {
lcp: {
good: args.lcpBudget || defaultBudget.lcp.good,
poor: (args.lcpBudget || defaultBudget.lcp.good) * 1.6
},
cls: {
good: args.clsBudget || defaultBudget.cls.good,
poor: (args.clsBudget || defaultBudget.cls.good) * 2.5
},
fcp: {
good: args.fcpBudget || defaultBudget.fcp.good,
poor: (args.fcpBudget || defaultBudget.fcp.good) * 1.7
},
inp: {
good: args.inpBudget || defaultBudget.inp?.good || 200,
poor: (args.inpBudget || defaultBudget.inp?.good || 200) * 2.5
},
ttfb: {
good: args.ttfbBudget || defaultBudget.ttfb.good,
poor: (args.ttfbBudget || defaultBudget.ttfb.good) * 2
}
};
}
// Template budget
const template = args.budget || 'default';
return web_vitals_collector_1.BUDGET_TEMPLATES[template] || web_vitals_collector_1.BUDGET_TEMPLATES.default;
}
showConfigurationSummary(config, args) {
console.log('\\nπ Configuration:');
console.log(` π Pages: ${config.maxPages === 1000 ? 'All' : config.maxPages}`);
console.log(` π Standard: ${config.pa11yStandard}`);
console.log(` π Performance: ${config.generatePerformanceReport ? 'Yes' : 'No'}`);
console.log(` π§ Queue System: Modern Queue`);
console.log(` π Format: ${config.outputFormat?.toUpperCase()}`);
console.log(` π Output: ${config.outputDir}`);
// Enhanced Analysis Summary
const enhancedConfig = config;
if (enhancedConfig.enhanced || args.enhanced) {
console.log('\\nπ Enhanced Analysis:');
if (enhancedConfig.enhancedPerformance || args.enhancedPerformance) {
console.log(' β‘ Enhanced Performance: Yes - Core Web Vitals, advanced metrics');
}
if (enhancedConfig.enhancedSeo || args.enhancedSeo) {
console.log(' π Enhanced SEO: Yes - Meta analysis, content quality, readability');
}
if (enhancedConfig.contentWeight || args.contentWeight) {
console.log(' π Content Weight: Yes - Resource analysis, text-to-code ratios');
}
}
}
async discoverSitemap(sitemapUrl) {
if (sitemapUrl.includes('sitemap.xml') || sitemapUrl.includes('sitemap')) {
return sitemapUrl;
}
this.logProgress('Discovering sitemap...');
const discovery = new sitemap_discovery_1.SitemapDiscovery();
const result = await discovery.discoverSitemap(sitemapUrl);
if (result.found) {
const finalUrl = result.sitemaps[0];
this.logSuccess(`Found sitemap: ${finalUrl} (method: ${result.method})`);
if (result.sitemaps.length > 1) {
this.logProgress(`Additional sitemaps found: ${result.sitemaps.length - 1}`);
}
return finalUrl;
}
else {
result.warnings.forEach(warning => this.logWarning(warning));
throw new Error('No sitemap found');
}
}
setupOutputDirectory(sitemapUrl, outputDir = './reports') {
const domain = this.extractDomain(sitemapUrl);
const subDir = path.join(outputDir, domain);
if (!fs.existsSync(subDir)) {
fs.mkdirSync(subDir, { recursive: true });
}
return { dir: subDir, domain };
}
async runAudit(sitemapUrl, config, outputInfo) {
const startTime = Date.now();
// Standard audit pipeline (enhanced analysis is now default in StandardPipeline)
this.logProgress('Starting accessibility test...');
const pipeline = new standard_pipeline_1.StandardPipeline();
const result = await pipeline.run({
...config,
sitemapUrl,
outputDir: outputInfo.dir
});
// Report generation is handled by the pipeline
const totalTime = Date.now() - startTime;
const avgSpeed = result.summary.testedPages / (totalTime / 60000); // pages per minute
this.logSuccess(`Completed ${result.summary.testedPages} pages in ${this.formatDuration(totalTime)}`);
this.logProgress(`Average speed: ${avgSpeed.toFixed(1)} pages/minute`);
return result;
}
async generateEnhancedReports(result, config, outputInfo) {
try {
this.logProgress('Generating enhanced analysis reports...');
// For now, create a simple enhanced report
// In the future, this should use an enhanced report generator
const reportPath = path.join(outputInfo.dir, 'enhanced-audit-report.html');
const htmlContent = this.generateSimpleEnhancedReport(result, config);
fs.writeFileSync(reportPath, htmlContent);
const sizeKB = Math.round(fs.statSync(reportPath).size / 1024);
this.logSuccess(`Generated enhanced HTML report: enhanced-audit-report.html (${sizeKB}KB)`);
result.outputFiles = [reportPath];
}
catch (error) {
this.logWarning(`Enhanced report generation failed: ${error}`);
this.logProgress('Enhanced analysis results are still available, only report generation failed');
}
}
generateSimpleEnhancedReport(result, config) {
const { summary, enhancedResults } = result;
return `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Enhanced Accessibility Analysis Report</title>
<style>
body { font-family: Arial, sans-serif; margin: 0; padding: 20px; background: #f5f5f5; }
.container { max-width: 1200px; margin: 0 auto; background: white; padding: 30px; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); }
h1 { color: #2563eb; border-bottom: 2px solid #2563eb; padding-bottom: 10px; }
.summary { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 20px; margin: 20px 0; }
.metric { background: #f8fafc; padding: 15px; border-radius: 8px; text-align: center; }
.metric-value { font-size: 2em; font-weight: bold; color: #2563eb; }
.metric-label { font-size: 0.9em; color: #64748b; margin-top: 5px; }
.results-table { width: 100%; border-collapse: collapse; margin: 20px 0; }
.results-table th, .results-table td { padding: 12px; text-align: left; border-bottom: 1px solid #e2e8f0; }
.results-table th { background: #f1f5f9; font-weight: 600; }
.grade { padding: 4px 8px; border-radius: 4px; color: white; font-weight: bold; }
.grade-A { background: #10b981; }
.grade-B { background: #3b82f6; }
.grade-C { background: #f59e0b; }
.grade-D { background: #ef4444; }
.grade-F { background: #991b1b; }
.enhanced-metrics { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 10px; font-size: 0.9em; }
.enhanced-metric { background: #f8fafc; padding: 8px; border-radius: 4px; text-align: center; }
</style>
</head>
<body>
<div class="container">
<h1>π Enhanced Accessibility Analysis Report</h1>
<div class="summary">
<div class="metric">
<div class="metric-value">${summary.testedPages}</div>
<div class="metric-label">Pages Tested</div>
</div>
<div class="metric">
<div class="metric-value">${summary.passedPages}</div>
<div class="metric-label">Passed</div>
</div>
<div class="metric">
<div class="metric-value">${summary.failedPages}</div>
<div class="metric-label">Failed</div>
</div>
<div class="metric">
<div class="metric-value">${Math.round((summary.passedPages / summary.testedPages) * 100)}%</div>
<div class="metric-label">Success Rate</div>
</div>
</div>
<h2>Detailed Results</h2>
<table class="results-table">
<thead>
<tr>
<th>Page</th>
<th>Status</th>
<th>Enhanced Metrics</th>
<th>Quality Score</th>
</tr>
</thead>
<tbody>
${enhancedResults.map((page) => `
<tr>
<td>
<strong>${page.title}</strong><br>
<small style="color: #64748b;">${page.url}</small>
</td>
<td>
${page.passed ? 'β
Passed' : 'β Failed'}
${page.errors ? `<br><small>${page.errors} errors</small>` : ''}
${page.warnings ? `<br><small>${page.warnings} warnings</small>` : ''}
</td>
<td>
<div class="enhanced-metrics">
${page.enhancedSEO ? `<div class="enhanced-metric">SEO: ${page.enhancedSEO.seoScore}/100</div>` : ''}
${page.contentWeight ? `<div class="enhanced-metric">Content: ${page.contentWeight.contentScore}/100</div>` : ''}
${page.enhancedPerformance ? `<div class="enhanced-metric">Performance: ${page.enhancedPerformance.performanceScore || 'N/A'}</div>` : ''}
</div>
</td>
<td>
${page.qualityScore ?
`<span class="grade grade-${page.qualityScore.grade}">${page.qualityScore.score}/100 (${page.qualityScore.grade})</span>` : 'N/A'}
</td>
</tr>
`).join('')}
</tbody>
</table>
<footer style="margin-top: 40px; padding-top: 20px; border-top: 1px solid #e2e8f0; text-align: center; color: #64748b;">
<p>Generated by AuditMySite Enhanced Analysis - ${new Date().toLocaleString()}</p>
</footer>
</div>
</body>
</html>`;
}
// generateUnifiedReports removed - using direct report generation in pipeline
showResults(result) {
const { summary, outputFiles } = result;
console.log('\\nπ Results:');
console.log(` π Tested: ${summary.testedPages} pages`);
console.log(` β
Passed: ${summary.passedPages}`);
console.log(` β Failed: ${summary.failedPages}`);
console.log(` β οΈ Errors: ${summary.totalErrors}`);
console.log(` β οΈ Warnings: ${summary.totalWarnings}`);
const successRate = summary.testedPages > 0 ?
(summary.passedPages / summary.testedPages * 100).toFixed(1) : 0;
console.log(` π― Success Rate: ${successRate}%`);
if (outputFiles.length > 0) {
console.log('\\nπ Generated reports:');
outputFiles.forEach((file) => {
console.log(` π ${path.basename(file)}`);
});
}
// Status summary
if (summary.crashedPages > 0) {
this.logError(`${summary.crashedPages} pages crashed due to technical errors`);
}
else if (summary.failedPages > 0) {
this.logWarning(`${summary.failedPages} pages failed accessibility tests (this is normal for real websites)`);
this.logProgress('Check the detailed report for specific issues to fix');
}
}
}
exports.AuditCommand = AuditCommand;
//# sourceMappingURL=audit-command.js.map