portfolio/check-qa.cjs

129 lines
4.3 KiB
JavaScript

const fs = require('fs');
const path = require('path');
const distDir = path.resolve(__dirname, 'dist');
console.log('--------------------------------------------------');
console.log('🤖 Running Automated QA Release Check...');
console.log('--------------------------------------------------');
// Helper to log test stages
function runStage(name, fn) {
try {
fn();
console.log(`✅ [PASS] ${name}`);
} catch (err) {
console.error(`❌ [FAIL] ${name}`);
console.error(` 👉 Reason: ${err.message}`);
process.exit(1);
}
}
// 1. Verify build directory exists
runStage('Build Directory Verification', () => {
if (!fs.existsSync(distDir)) {
throw new Error('Build output directory "dist/" does not exist. Run "npm run build" first.');
}
});
const indexHtmlPath = path.join(distDir, 'index.html');
let htmlContent = '';
// 2. Verify index.html exists and is readable
runStage('index.html Integrity Check', () => {
if (!fs.existsSync(indexHtmlPath)) {
throw new Error('index.html is missing from dist/ folder.');
}
htmlContent = fs.readFileSync(indexHtmlPath, 'utf8');
if (!htmlContent || htmlContent.trim().length === 0) {
throw new Error('index.html is empty.');
}
});
// 3. Check for SEO and duplicate tag bugs
runStage('SEO & Metadata Tag Checks', () => {
// Check for duplicate <title> tags
const titleMatches = htmlContent.match(/<title[^>]*>([\s\S]*?)<\/title>/gi);
if (!titleMatches) {
throw new Error('No <title> tag found in index.html.');
}
if (titleMatches.length > 1) {
throw new Error(`Duplicate <title> tags detected! Found ${titleMatches.length} title tags.`);
}
// Ensure title contains Rafael's name
const titleText = titleMatches[0];
if (!titleText.toLowerCase().includes('rafael')) {
throw new Error('Optimized title does not contain "Rafael" - SEO validation failed.');
}
// Check for duplicate descriptions
const descMatches = htmlContent.match(/<meta\s+name="description"\s+content="[^"]*"\s*\/?>/gi);
if (descMatches && descMatches.length > 1) {
throw new Error(`Duplicate meta description tags detected! Found ${descMatches.length} descriptions.`);
}
});
// 4. Verify critical routing and search engine assets
runStage('Core Assets Verification', () => {
const criticalFiles = ['.htaccess', 'robots.txt', 'sitemap.xml'];
for (const file of criticalFiles) {
const filePath = path.join(distDir, file);
if (!fs.existsSync(filePath)) {
throw new Error(`Critical file "${file}" is missing from the build directory.`);
}
}
});
// 5. Verify local asset references exist in dist/
runStage('Broken Reference & Asset Verification', () => {
// Regex to match local src and href attributes in index.html
const linkRegex = /(src|href)="\/([^"]+)"/g;
let match;
const verifiedAssets = [];
while ((match = linkRegex.exec(htmlContent)) !== null) {
const assetPath = match[2];
// Skip absolute links, PDF documents, and external URLs
if (
assetPath.startsWith('http') ||
assetPath.startsWith('//') ||
assetPath.endsWith('.pdf') ||
assetPath.startsWith('sitemap') ||
assetPath.startsWith('robots')
) {
continue;
}
const fullAssetPath = path.join(distDir, assetPath.split('?')[0]);
if (!fs.existsSync(fullAssetPath)) {
throw new Error(`Broken link/reference detected! File "/${assetPath}" was referenced in index.html but does not exist in the build output.`);
}
verifiedAssets.push(assetPath);
}
if (verifiedAssets.length > 0) {
console.log(` 👉 Verified ${verifiedAssets.length} asset references correctly.`);
}
});
// 6. Verify language flags exist
runStage('Locale Flag Image Integrity Check', () => {
const flagsDir = path.join(distDir, 'flags');
if (!fs.existsSync(flagsDir)) {
throw new Error('flags/ directory is missing from build output.');
}
const requiredFlags = ['gb.png', 'es.png', 'it.png', 'de.png'];
for (const flag of requiredFlags) {
const flagPath = path.join(flagsDir, flag);
if (!fs.existsSync(flagPath)) {
throw new Error(`Required country flag image "${flag}" is missing from flags/ directory.`);
}
}
});
console.log('--------------------------------------------------');
console.log('🎉 QA Release Check complete. Build is healthy!');
console.log('--------------------------------------------------');
process.exit(0);