@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
424 lines โข 17.4 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;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.CoreAuditPipeline = void 0;
const parsers_1 = require("../parsers");
const audit_results_1 = require("../../types/audit-results");
const path = __importStar(require("path"));
const fs = __importStar(require("fs/promises"));
// Types are now imported from shared audit-results.ts
// This ensures consistency across CLI JSON, HTML reports, and API
/**
* ๐ Core Audit Pipeline v2.0
*
* Simplified, maintainable pipeline focused on:
* 1. Sitemap audit (CORE)
* 2. JSON export (PRIORITY 1)
* 3. HTML report (PRIORITY 2)
*
* Key improvements:
* - Reduced from 286 to <150 lines
* - Only 2 queue modes (Standard vs Enhanced)
* - Consistent JSON output structure
* - Async file operations
* - Single browser manager instance
*/
class CoreAuditPipeline {
/**
* Main pipeline execution
*/
async run(options) {
const startTime = Date.now();
console.log('๐ฏ AuditMySite v2.0 - Core Pipeline');
console.log(`๐ Analyzing: ${options.sitemapUrl}`);
// 1. Parse sitemap and create SitemapResult
const sitemapResult = await this.parseSitemap(options.sitemapUrl, options);
const urls = sitemapResult.urls;
console.log(`๐ Testing ${urls.length} pages (filtered: ${sitemapResult.filteredUrls} excluded)`);
// 2. Initialize browser manager (single instance)
const browserManager = await this.initializeBrowserPool();
try {
// 3. Run audit (unified approach - all features in AccessibilityChecker)
const results = await this.runAccessibilityAudit(urls, browserManager, options);
// 4. Create full audit result (JSON structure)
const fullResult = this.buildFullAuditResult(results, sitemapResult, options, startTime);
// 5. Export JSON (CORE FUNCTIONALITY)
const jsonPath = await this.exportToJSON(fullResult, options);
// 6. Optional: Generate HTML report (reads JSON)
if (options.generateHTML) {
await this.generateHTMLReport(jsonPath, options);
}
const duration = Date.now() - startTime;
console.log(`โ
Audit completed in ${Math.round(duration / 1000)}s`);
return fullResult;
}
finally {
// 7. Cleanup browser resources
await this.cleanupBrowserPool(browserManager);
}
}
/**
* Parse sitemap and return structured result
*/
async parseSitemap(sitemapUrl, options) {
const parser = new parsers_1.SitemapParser();
const rawUrls = await parser.parseSitemap(sitemapUrl);
const allUrls = rawUrls.map((url) => url.loc || url);
console.log(`๐ Sitemap loaded: ${allUrls.length} URLs found`);
// Apply filters and limits
const filterPatterns = ['[...slug]', '[category]', '/demo/', '/test/'];
const filteredUrls = parser.filterUrls(allUrls.map(url => ({ loc: url })), { filterPatterns });
// Limit to maxPages
const maxPages = options.maxPages || 20;
const limitedUrls = filteredUrls.slice(0, maxPages);
const finalUrls = limitedUrls.map((url) => url.loc);
return {
sourceUrl: sitemapUrl,
urls: finalUrls,
parsedAt: new Date().toISOString(),
totalUrls: allUrls.length,
filteredUrls: allUrls.length - filteredUrls.length,
filterPatterns
};
}
/**
* Initialize browser pool (optimized for v2.0)
*/
async initializeBrowserPool() {
console.log('๐ Initializing optimized browser pool...');
// Use optimized BrowserPoolManager instead of single browser
const { BrowserPoolManager } = require('../browser/browser-pool-manager');
const poolManager = new BrowserPoolManager({
maxConcurrent: 3, // Conservative for stability
maxIdleTime: 30000, // 30 seconds
browserType: 'chromium',
enableResourceOptimization: true,
launchOptions: {
headless: true,
args: [
'--no-sandbox',
'--disable-setuid-sandbox',
'--disable-dev-shm-usage',
'--disable-gpu',
'--memory-pressure-off',
'--max_old_space_size=2048' // Reduced from 4GB to 2GB
]
}
});
// Warm up the pool with 1 browser initially
await poolManager.warmUp(1);
return poolManager;
}
/**
* Run accessibility audit with all features - OPTIMIZED v2.0
*/
async runAccessibilityAudit(urls, poolManager, options) {
console.log('๐ง Running Accessibility Audit with Full Features...');
// Use new pooled accessibility checker
const { AccessibilityChecker } = require('../accessibility/accessibility-checker');
const accessibilityChecker = new AccessibilityChecker({ usePooling: true, poolManager });
// No need to call initialize() for pooled checker - pool is already initialized
const testOptions = {
maxPages: options.maxPages || 20,
timeout: options.timeout || 10000,
waitUntil: 'domcontentloaded',
pa11yStandard: options.pa11yStandard || 'WCAG2AA',
captureScreenshots: options.captureScreenshots || false,
includeWarnings: options.includeWarnings !== false,
maxConcurrent: options.maxConcurrent || 3,
verbose: false // Reduce console output for performance
};
// Direct pool-based testing (no complex queue system)
const results = await accessibilityChecker.testMultiplePages(urls, testOptions);
return results;
}
/**
* Build full audit result with strict typing
*/
buildFullAuditResult(results, sitemapResult, options, startTime) {
const duration = Date.now() - startTime;
const pages = results.map(r => this.mapToPageAuditResult(r));
// Calculate summary statistics
const passedPages = pages.filter(p => p.status === 'passed').length;
const failedPages = pages.filter(p => p.status === 'failed').length;
const crashedPages = pages.filter(p => p.status === 'crashed').length;
const totalErrors = pages.reduce((sum, p) => sum + p.accessibility.errors.length, 0);
const totalWarnings = pages.reduce((sum, p) => sum + p.accessibility.warnings.length, 0);
// Calculate average scores
const accessibilityScores = pages.map(p => p.accessibility.score).filter(s => s !== undefined);
const avgAccessibilityScore = accessibilityScores.length > 0
? Math.round(accessibilityScores.reduce((a, b) => a + b, 0) / accessibilityScores.length)
: 0;
return {
metadata: {
timestamp: new Date().toISOString(),
version: '2.0',
sitemapUrl: options.sitemapUrl,
duration,
toolVersion: require('../../../package.json').version,
config: {
maxPages: options.maxPages || 20,
fullAnalysis: true,
pa11yStandard: options.pa11yStandard || 'WCAG2AA',
analysisTypes: {
accessibility: true,
performance: options.collectPerformanceMetrics || false,
seo: true,
contentWeight: true
}
}
},
sitemap: sitemapResult,
pages,
summary: {
totalPages: sitemapResult.totalUrls,
testedPages: pages.length,
passedPages,
failedPages,
crashedPages,
totalErrors,
totalWarnings,
averageScores: {
accessibility: avgAccessibilityScore
},
overallGrades: {
accessibility: (0, audit_results_1.calculateGrade)(avgAccessibilityScore)
}
}
};
}
/**
* Map legacy AccessibilityResult to typed PageAuditResult
*/
mapToPageAuditResult(result) {
// Determine status
let status = 'passed';
if (result.crashed) {
status = 'crashed';
}
else if (!result.passed) {
status = 'failed';
}
// Map accessibility data to new structure
const accessibilityResult = {
passed: result.passed,
wcagLevel: 'AA', // Default, could be enhanced based on actual analysis
score: this.calculateAccessibilityScore(result),
errors: (result.errors || []).map(error => ({
severity: 'error',
message: error,
code: 'a11y-error'
})),
warnings: (result.warnings || []).map(warning => ({
severity: 'warning',
message: warning,
code: 'a11y-warning'
})),
pa11yResults: {
totalIssues: (result.errors?.length || 0) + (result.warnings?.length || 0),
runner: 'pa11y@9.0.0'
}
};
return {
url: result.url,
title: result.title,
status,
duration: result.duration || 0,
auditedAt: new Date().toISOString(),
accessibility: accessibilityResult,
// Optional enhanced data (if available)
performance: result.performanceMetrics ? this.mapPerformanceResult(result.performanceMetrics) : undefined,
seo: result.enhancedSEO ? this.mapSEOResult(result.enhancedSEO) : undefined,
contentWeight: result.contentWeight ? this.mapContentWeightResult(result.contentWeight) : undefined
};
}
/**
* Calculate accessibility score from legacy result
*/
calculateAccessibilityScore(result) {
const errors = result.errors?.length || 0;
const warnings = result.warnings?.length || 0;
if (errors === 0 && warnings === 0)
return 100;
// Deduct points for issues (errors are weighted more heavily)
const score = Math.max(0, 100 - (errors * 10) - (warnings * 2));
return Math.round(score);
}
/**
* Map legacy performance data to PerformanceResult (stub)
*/
mapPerformanceResult(metrics) {
if (!metrics)
return undefined;
return {
score: 75, // Placeholder
grade: 'C',
coreWebVitals: {
largestContentfulPaint: metrics.largestContentfulPaint || 0,
firstContentfulPaint: metrics.firstContentfulPaint || 0,
cumulativeLayoutShift: metrics.cumulativeLayoutShift || 0,
timeToFirstByte: metrics.timeToFirstByte || 0
},
metrics: {
domContentLoaded: metrics.domContentLoaded || 0,
loadComplete: metrics.loadTime || 0
},
issues: []
};
}
/**
* Map legacy SEO data to SEOResult (stub)
*/
mapSEOResult(seoData) {
if (!seoData)
return undefined;
return {
score: 80, // Placeholder
grade: 'B',
metaTags: {
title: seoData.title ? {
content: seoData.title,
length: seoData.title.length,
optimal: seoData.title.length >= 10 && seoData.title.length <= 60
} : undefined,
openGraph: {},
twitterCard: {}
},
headings: {
h1: [],
h2: [],
h3: [],
issues: []
},
images: {
total: 0,
missingAlt: 0,
emptyAlt: 0
},
issues: []
};
}
/**
* Map legacy content weight data to ContentWeightResult (stub)
*/
mapContentWeightResult(contentData) {
if (!contentData)
return undefined;
return {
score: 85, // Placeholder
grade: 'B',
totalSize: contentData.totalSize || 0,
resources: {
html: { size: 0 },
css: { size: 0, files: 0 },
javascript: { size: 0, files: 0 },
images: { size: 0, files: 0 },
other: { size: 0, files: 0 }
},
optimizations: []
};
}
/**
* Export to JSON (CORE FUNCTIONALITY) - returns file path for HTML generator
*/
async exportToJSON(result, options) {
const outputDir = options.outputDir || './reports';
await fs.mkdir(outputDir, { recursive: true });
const dateOnly = new Date().toISOString().split('T')[0];
const filename = `audit-result-${dateOnly}.json`;
const filePath = path.join(outputDir, filename);
const jsonContent = JSON.stringify(result, null, 2);
await fs.writeFile(filePath, jsonContent, 'utf8');
console.log(`๐ JSON exported: ${filePath}`);
return filePath;
}
/**
* Generate HTML report from JSON file (Sprint 2 architecture)
*/
async generateHTMLReport(jsonPath, options) {
console.log('๐ Generating HTML report from JSON...');
// Use HTMLGenerator (current standard)
const { HTMLGenerator } = require('../../generators/html-generator');
const generator = new HTMLGenerator();
const htmlContent = await generator.generateFromJSON(jsonPath);
const outputDir = options.outputDir || './reports';
const dateOnly = new Date().toISOString().split('T')[0];
const htmlPath = path.join(outputDir, `audit-report-${dateOnly}.html`);
await fs.writeFile(htmlPath, htmlContent, 'utf8');
console.log(`๐ HTML exported: ${htmlPath}`);
}
/**
* Convert FullAuditResult to legacy format (temporary compatibility)
*/
convertToLegacyFormat(result) {
return {
summary: {
totalPages: result.summary.totalPages,
testedPages: result.summary.testedPages,
passedPages: result.summary.passedPages,
failedPages: result.summary.failedPages,
crashedPages: result.summary.crashedPages,
totalErrors: result.summary.totalErrors,
totalWarnings: result.summary.totalWarnings,
totalDuration: result.metadata.duration,
results: result.pages.map(page => ({
url: page.url,
title: page.title,
passed: page.accessibility.passed,
crashed: page.status === 'crashed',
errors: page.accessibility.errors.map(e => e.message),
warnings: page.accessibility.warnings.map(w => w.message),
duration: page.duration,
performanceMetrics: page.performance?.coreWebVitals
}))
},
pages: result.pages,
metadata: result.metadata
};
}
/**
* Cleanup browser resources
*/
async cleanupBrowserPool(poolManager) {
if (poolManager) {
console.log('๐งผ Shutting down browser pool...');
const metrics = poolManager.getMetrics();
console.log(`๐ Pool efficiency: ${metrics.efficiency.toFixed(1)}% (${metrics.reused}/${metrics.totalRequests} reused)`);
await poolManager.shutdown();
}
}
}
exports.CoreAuditPipeline = CoreAuditPipeline;
//# sourceMappingURL=core-audit-pipeline.js.map