177 lines
5.2 KiB
JavaScript
Executable File
177 lines
5.2 KiB
JavaScript
Executable File
#!/usr/bin/env node
|
|
|
|
/**
|
|
* Roier SEO - PageSpeed Insights API Audit
|
|
*
|
|
* Uses Google's PageSpeed Insights API (no local Chrome needed).
|
|
* Works for any publicly accessible URL.
|
|
*
|
|
* Usage:
|
|
* node audit-api.js <URL> [--key=API_KEY]
|
|
*
|
|
* Note: Without an API key, you're limited to ~5 requests/minute.
|
|
* Get a free API key at: https://developers.google.com/speed/docs/insights/v5/get-started
|
|
*
|
|
* Requirements: Node.js 18+ (uses native fetch)
|
|
*/
|
|
|
|
// Check Node.js version - native fetch requires Node 18+
|
|
const nodeVersion = parseInt(process.versions.node.split('.')[0], 10);
|
|
if (nodeVersion < 18) {
|
|
console.error('Error: This script requires Node.js 18 or higher (for native fetch support).');
|
|
console.error(`Current version: ${process.versions.node}`);
|
|
process.exit(1);
|
|
}
|
|
|
|
const API_URL = 'https://www.googleapis.com/pagespeedonline/v5/runPagespeed';
|
|
|
|
const colors = {
|
|
reset: '\x1b[0m',
|
|
red: '\x1b[31m',
|
|
green: '\x1b[32m',
|
|
yellow: '\x1b[33m',
|
|
cyan: '\x1b[36m',
|
|
bold: '\x1b[1m',
|
|
dim: '\x1b[2m'
|
|
};
|
|
|
|
async function runAudit(url, apiKey = null) {
|
|
console.error(`${colors.cyan}🔍 Running PageSpeed Insights audit for: ${url}${colors.reset}\n`);
|
|
|
|
const categories = ['performance', 'accessibility', 'best-practices', 'seo', 'pwa'];
|
|
const params = new URLSearchParams({
|
|
url,
|
|
strategy: 'mobile',
|
|
...Object.fromEntries(categories.map(c => [`category`, c]))
|
|
});
|
|
|
|
// Add all categories
|
|
const categoryParams = categories.map(c => `category=${c}`).join('&');
|
|
let apiUrl = `${API_URL}?url=${encodeURIComponent(url)}&strategy=mobile&${categoryParams}`;
|
|
|
|
if (apiKey) {
|
|
apiUrl += `&key=${apiKey}`;
|
|
}
|
|
|
|
try {
|
|
const response = await fetch(apiUrl);
|
|
|
|
if (!response.ok) {
|
|
const error = await response.json();
|
|
throw new Error(error.error?.message || `API error: ${response.status}`);
|
|
}
|
|
|
|
const data = await response.json();
|
|
const lhr = data.lighthouseResult;
|
|
|
|
// Extract scores
|
|
const scores = {
|
|
performance: Math.round((lhr.categories.performance?.score || 0) * 100),
|
|
accessibility: Math.round((lhr.categories.accessibility?.score || 0) * 100),
|
|
bestPractices: Math.round((lhr.categories['best-practices']?.score || 0) * 100),
|
|
seo: Math.round((lhr.categories.seo?.score || 0) * 100),
|
|
pwa: Math.round((lhr.categories.pwa?.score || 0) * 100)
|
|
};
|
|
|
|
// Extract failed audits
|
|
const issues = {
|
|
critical: [],
|
|
serious: [],
|
|
moderate: [],
|
|
minor: []
|
|
};
|
|
|
|
for (const [auditId, audit] of Object.entries(lhr.audits)) {
|
|
if (audit.score !== null && audit.score < 1) {
|
|
const issue = {
|
|
id: auditId,
|
|
title: audit.title,
|
|
description: audit.description,
|
|
score: audit.score,
|
|
displayValue: audit.displayValue || null
|
|
};
|
|
|
|
if (audit.details?.items) {
|
|
issue.items = audit.details.items.slice(0, 3);
|
|
}
|
|
|
|
if (audit.score === 0) {
|
|
issues.critical.push(issue);
|
|
} else if (audit.score < 0.5) {
|
|
issues.serious.push(issue);
|
|
} else if (audit.score < 0.9) {
|
|
issues.moderate.push(issue);
|
|
} else {
|
|
issues.minor.push(issue);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Core Web Vitals
|
|
const webVitals = {
|
|
FCP: lhr.audits['first-contentful-paint']?.numericValue,
|
|
LCP: lhr.audits['largest-contentful-paint']?.numericValue,
|
|
TBT: lhr.audits['total-blocking-time']?.numericValue,
|
|
CLS: lhr.audits['cumulative-layout-shift']?.numericValue,
|
|
SI: lhr.audits['speed-index']?.numericValue
|
|
};
|
|
|
|
const result = {
|
|
url,
|
|
fetchTime: lhr.fetchTime,
|
|
scores,
|
|
webVitals,
|
|
issues,
|
|
summary: {
|
|
totalIssues: issues.critical.length + issues.serious.length + issues.moderate.length + issues.minor.length,
|
|
critical: issues.critical.length,
|
|
serious: issues.serious.length,
|
|
moderate: issues.moderate.length,
|
|
minor: issues.minor.length
|
|
}
|
|
};
|
|
|
|
// Output JSON to stdout
|
|
console.log(JSON.stringify(result, null, 2));
|
|
|
|
return result;
|
|
|
|
} catch (error) {
|
|
console.error(`${colors.red}❌ Audit failed: ${error.message}${colors.reset}`);
|
|
process.exit(1);
|
|
}
|
|
}
|
|
|
|
// Parse arguments
|
|
const args = process.argv.slice(2);
|
|
const url = args.find(arg => !arg.startsWith('--'));
|
|
const keyFlag = args.find(arg => arg.startsWith('--key='));
|
|
const apiKey = keyFlag ? keyFlag.split('=')[1] : null;
|
|
|
|
if (!url) {
|
|
console.log(`
|
|
${colors.cyan}Roier SEO - PageSpeed Insights API Audit${colors.reset}
|
|
|
|
${colors.bold}Usage:${colors.reset}
|
|
node audit-api.js <URL> [--key=API_KEY]
|
|
|
|
${colors.bold}Examples:${colors.reset}
|
|
node audit-api.js https://example.com
|
|
node audit-api.js https://example.com --key=YOUR_API_KEY
|
|
|
|
${colors.bold}Note:${colors.reset}
|
|
- Only works for publicly accessible URLs
|
|
- Without an API key, limited to ~5 requests/minute
|
|
- Get a free API key at: https://developers.google.com/speed/docs/insights/v5/get-started
|
|
`);
|
|
process.exit(0);
|
|
}
|
|
|
|
if (url.includes('localhost') || url.includes('127.0.0.1')) {
|
|
console.error(`${colors.yellow}⚠️ Warning: PageSpeed Insights API cannot audit localhost URLs.`);
|
|
console.error(` Use 'node audit.js' (with local Chrome) for localhost audits.${colors.reset}\n`);
|
|
process.exit(1);
|
|
}
|
|
|
|
runAudit(url, apiKey);
|