Files
boc/landvex-quixzoom-cms/src/scripts/seo-audit.ts
T
Bernt 6989a98d75 feat: Passwordless cross-device authentication
- Arkitektur: docs/auth/passwordless-architecture.md
- Backend: iom/quixzoom-auth-service/ (FastAPI + Redis)
- Webb: quixzoom-market-pages/se/login/ (QR-kod + polling)
- App: iom/quixzoom-app/src/features/auth/ (push + deep links)

Flöde: QR-kod → app-godkännande → webb-inloggad
2026-07-07 07:11:50 +00:00

139 lines
4.0 KiB
TypeScript

/**
* SEO Audit Script
* Runs comprehensive SEO audits across all content
*/
import { db } from '../db/index.js';
import { pages, translations } from '../db/schema.js';
import { eq } from 'drizzle-orm';
import { runSeoAudit, generateSitemap } from '../services/seo-service.js';
import { ALL_SITES } from '../config/sites.js';
import { writeFileSync, mkdirSync } from 'fs';
import { join } from 'path';
async function runSeoAuditBatch(options: {
domain?: string;
language?: string;
generateSitemaps?: boolean;
outputDir?: string;
} = {}) {
console.log('🔍 Starting SEO audit...\n');
const outputDir = options.outputDir || './output/seo';
mkdirSync(outputDir, { recursive: true });
// Audit source pages
const sourcePages = db.select().from(pages)
.where(eq(pages.status, 'published'))
.all();
console.log(`Auditing ${sourcePages.length} source pages...\n`);
const results: Array<{
url: string;
language: string;
score: number;
issues: number;
}> = [];
for (let i = 0; i < sourcePages.length; i++) {
const page = sourcePages[i];
console.log(` [${i + 1}/${sourcePages.length}] ${page.title}`);
try {
// Audit source
const sourceAudit = runSeoAudit(page.id);
results.push({
url: sourceAudit.url,
language: sourceAudit.language,
score: sourceAudit.score,
issues: sourceAudit.issues.length
});
// Audit translations
const pageTranslations = db.select().from(translations)
.where(eq(translations.pageId, page.id))
.all();
for (const translation of pageTranslations) {
if (options.language && translation.targetLanguage !== options.language) continue;
const transAudit = runSeoAudit(page.id, translation.targetLanguage);
results.push({
url: transAudit.url,
language: transAudit.language,
score: transAudit.score,
issues: transAudit.issues.length
});
}
} catch (err) {
console.log(` ✗ Error: ${err instanceof Error ? err.message : 'Unknown'}`);
}
}
// Summary
const avgScore = results.length > 0
? results.reduce((sum, r) => sum + r.score, 0) / results.length
: 0;
const lowScorePages = results.filter(r => r.score < 70);
console.log(`\n📊 SEO Audit Summary`);
console.log(` Total audited: ${results.length}`);
console.log(` Average score: ${Math.round(avgScore * 10) / 10}/100`);
console.log(` Pages needing attention: ${lowScorePages.length}`);
if (lowScorePages.length > 0) {
console.log(`\n⚠️ Low scores:`);
lowScorePages.forEach(r => {
console.log(` - ${r.url} (${r.language}): ${Math.round(r.score)}/100`);
});
}
// Generate sitemaps
if (options.generateSitemaps) {
console.log(`\n🗺️ Generating sitemaps...`);
for (const site of ALL_SITES) {
const sitemap = generateSitemap(site.domain);
const filename = join(outputDir, `sitemap-${site.domain}.xml`);
writeFileSync(filename, sitemap);
console.log(`${filename}`);
}
}
// Save report
const report = {
timestamp: new Date().toISOString(),
summary: {
totalAudited: results.length,
averageScore: Math.round(avgScore * 10) / 10,
needsAttention: lowScorePages.length
},
results
};
const reportFile = join(outputDir, `audit-report-${new Date().toISOString().split('T')[0]}.json`);
writeFileSync(reportFile, JSON.stringify(report, null, 2));
console.log(`\n📝 Report saved: ${reportFile}`);
console.log('\n✅ SEO audit complete!');
}
// CLI
const args = process.argv.slice(2);
const options: {
domain?: string;
language?: string;
generateSitemaps?: boolean;
} = {};
if (args.includes('--sitemaps')) options.generateSitemaps = true;
const domainArg = args.find(a => a.startsWith('--domain='));
if (domainArg) options.domain = domainArg.split('=')[1];
const langArg = args.find(a => a.startsWith('--lang='));
if (langArg) options.language = langArg.split('=')[1];
runSeoAuditBatch(options).catch(console.error);