First Init
Deploy to Production / Build & Verify (push) Failing after 5m56s
Ping Search Engines / Notify Search Engines (push) Successful in 2s
Deploy to Production / Pre-Deploy Tests (push) Has been skipped
Deploy to Production / Deploy to Railway (push) Has been skipped
Deploy to Production / Deploy to Render (push) Has been skipped
Deploy to Production / Deploy to VPS (PM2) (push) Has been skipped
Deploy to Production / Deploy to Fly.io (push) Has been skipped
Deploy to Production / Post-Deploy Verification (push) Has been skipped
Deploy to Production / Notify on Failure (push) Successful in 2s
E2E Test Suite / Critical User Journeys (push) Has been skipped
E2E Test Suite / API Integration Tests (push) Has been skipped
E2E Test Suite / Smoke Tests (P0) (push) Failing after 11m26s
E2E Test Suite / Form Interaction Tests (push) Failing after 11m42s
E2E Test Suite / Destructive & Chaos Tests (push) Failing after 12m2s
E2E Test Suite / Cross-Browser Regression (chromium) (push) Failing after 16m14s
E2E Test Suite / Cross-Browser Regression (webkit) (push) Failing after 17m45s
E2E Test Suite / Cross-Browser Regression (firefox) (push) Failing after 25m23s
E2E Test Suite / Security Header Tests (push) Failing after 7m55s
E2E Test Suite / Test Report Summary (push) Failing after 20s
E2E Test Suite / Mobile Device Tests (push) Failing after 2h49m9s
Uptime Monitor / Health & Response Time (push) Failing after 2s
Uptime Monitor / SSL Certificate (push) Successful in 2s
Uptime Monitor / Send Alerts (push) Failing after 3s
Uptime Monitor / Record Uptime Success (push) Has been skipped

This commit is contained in:
2026-03-21 16:46:46 +05:30
commit d402256547
216 changed files with 48375 additions and 0 deletions
+341
View File
@@ -0,0 +1,341 @@
#!/usr/bin/env node
/**
* Schema Validation Script
*
* Validates JSON-LD structured data on all pages
* Checks for common issues and provides recommendations
*
* Usage: node scripts/validate-schema.js
*/
import { JSDOM } from 'jsdom';
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const COLORS = {
reset: '\x1b[0m',
red: '\x1b[31m',
green: '\x1b[32m',
yellow: '\x1b[33m',
blue: '\x1b[34m',
cyan: '\x1b[36m',
};
const log = {
success: (msg) => console.log(`${COLORS.green}${COLORS.reset} ${msg}`),
error: (msg) => console.log(`${COLORS.red}${COLORS.reset} ${msg}`),
warning: (msg) => console.log(`${COLORS.yellow}${COLORS.reset} ${msg}`),
info: (msg) => console.log(`${COLORS.blue}${COLORS.reset} ${msg}`),
section: (msg) => console.log(`\n${COLORS.cyan}${msg}${COLORS.reset}\n`),
};
/**
* Extract JSON-LD scripts from HTML
*/
function extractJsonLd(html) {
const dom = new JSDOM(html);
const scripts = dom.window.document.querySelectorAll('script[type="application/ld+json"]');
return Array.from(scripts).map(script => {
try {
return JSON.parse(script.textContent);
} catch (e) {
log.error(`Failed to parse JSON-LD: ${e.message}`);
return null;
}
}).filter(Boolean);
}
/**
* Validate required fields for each schema type
*/
function validateSchema(schema) {
const issues = [];
const warnings = [];
if (!schema['@context']) {
issues.push('Missing @context');
}
if (!schema['@type']) {
issues.push('Missing @type');
}
const type = schema['@type'];
// Validate based on type
switch (type) {
case 'Organization':
if (!schema.name) issues.push('Organization: Missing name');
if (!schema.url) issues.push('Organization: Missing url');
if (!schema.logo) warnings.push('Organization: Consider adding logo');
if (!schema.contactPoint) warnings.push('Organization: Consider adding contactPoint');
break;
case 'BlogPosting':
if (!schema.headline) issues.push('BlogPosting: Missing headline');
if (!schema.author) issues.push('BlogPosting: Missing author');
if (!schema.datePublished) issues.push('BlogPosting: Missing datePublished');
if (!schema.publisher) issues.push('BlogPosting: Missing publisher');
if (!schema.image) warnings.push('BlogPosting: Consider adding image');
break;
case 'BreadcrumbList':
if (!schema.itemListElement || !Array.isArray(schema.itemListElement)) {
issues.push('BreadcrumbList: Missing or invalid itemListElement');
} else {
schema.itemListElement.forEach((item, idx) => {
if (!item.position) issues.push(`BreadcrumbList: Item ${idx} missing position`);
if (!item.name) issues.push(`BreadcrumbList: Item ${idx} missing name`);
if (!item.item) issues.push(`BreadcrumbList: Item ${idx} missing item URL`);
});
}
break;
case 'WebPage':
if (!schema.name) issues.push('WebPage: Missing name');
if (!schema.url) issues.push('WebPage: Missing url');
if (!schema.description) warnings.push('WebPage: Consider adding description');
break;
case 'FAQPage':
if (!schema.mainEntity || !Array.isArray(schema.mainEntity)) {
issues.push('FAQPage: Missing or invalid mainEntity');
} else {
schema.mainEntity.forEach((item, idx) => {
if (item['@type'] !== 'Question') {
issues.push(`FAQPage: Item ${idx} should be type Question`);
}
if (!item.name) issues.push(`FAQPage: Question ${idx} missing name`);
if (!item.acceptedAnswer) {
issues.push(`FAQPage: Question ${idx} missing acceptedAnswer`);
}
});
}
break;
case 'Service':
if (!schema.name) issues.push('Service: Missing name');
if (!schema.description) issues.push('Service: Missing description');
if (!schema.provider) warnings.push('Service: Consider adding provider');
break;
case 'WebSite':
if (!schema.name) issues.push('WebSite: Missing name');
if (!schema.url) issues.push('WebSite: Missing url');
if (!schema.potentialAction) warnings.push('WebSite: Consider adding search action');
break;
}
return { issues, warnings };
}
/**
* Validate Open Graph tags
*/
function validateOpenGraph(html) {
const dom = new JSDOM(html);
const doc = dom.window.document;
const issues = [];
const warnings = [];
const requiredOgTags = ['og:title', 'og:description', 'og:image', 'og:url', 'og:type'];
requiredOgTags.forEach(tag => {
const meta = doc.querySelector(`meta[property="${tag}"]`);
if (!meta) {
issues.push(`Missing required Open Graph tag: ${tag}`);
}
});
const ogImage = doc.querySelector('meta[property="og:image"]');
if (ogImage) {
const imageUrl = ogImage.getAttribute('content');
if (!imageUrl.startsWith('http')) {
warnings.push('og:image should be an absolute URL');
}
if (!doc.querySelector('meta[property="og:image:width"]')) {
warnings.push('Consider adding og:image:width');
}
if (!doc.querySelector('meta[property="og:image:height"]')) {
warnings.push('Consider adding og:image:height');
}
}
return { issues, warnings };
}
/**
* Validate Twitter Card tags
*/
function validateTwitterCard(html) {
const dom = new JSDOM(html);
const doc = dom.window.document;
const issues = [];
const warnings = [];
const requiredTwitterTags = ['twitter:card', 'twitter:title', 'twitter:description', 'twitter:image'];
requiredTwitterTags.forEach(tag => {
const meta = doc.querySelector(`meta[name="${tag}"]`);
if (!meta) {
issues.push(`Missing required Twitter Card tag: ${tag}`);
}
});
const twitterCard = doc.querySelector('meta[name="twitter:card"]');
if (twitterCard && twitterCard.getAttribute('content') === 'summary_large_image') {
const twitterImage = doc.querySelector('meta[name="twitter:image"]');
if (twitterImage) {
const imageUrl = twitterImage.getAttribute('content');
if (!imageUrl.startsWith('http')) {
warnings.push('twitter:image should be an absolute URL');
}
}
}
return { issues, warnings };
}
/**
* Main validation function
*/
async function validatePage(filePath, pageName) {
log.section(`Validating: ${pageName}`);
try {
const html = fs.readFileSync(filePath, 'utf-8');
// Extract and validate JSON-LD
const schemas = extractJsonLd(html);
if (schemas.length === 0) {
log.warning('No JSON-LD structured data found');
} else {
log.info(`Found ${schemas.length} JSON-LD schema(s)`);
schemas.forEach((schema, idx) => {
console.log(`\n Schema ${idx + 1}: ${schema['@type'] || 'Unknown'}`);
const { issues, warnings } = validateSchema(schema);
if (issues.length === 0) {
log.success('No critical issues');
} else {
issues.forEach(issue => log.error(` ${issue}`));
}
if (warnings.length > 0) {
warnings.forEach(warning => log.warning(` ${warning}`));
}
});
}
// Validate Open Graph
console.log('\n Open Graph:');
const ogValidation = validateOpenGraph(html);
if (ogValidation.issues.length === 0) {
log.success('All required Open Graph tags present');
} else {
ogValidation.issues.forEach(issue => log.error(` ${issue}`));
}
if (ogValidation.warnings.length > 0) {
ogValidation.warnings.forEach(warning => log.warning(` ${warning}`));
}
// Validate Twitter Cards
console.log('\n Twitter Card:');
const twitterValidation = validateTwitterCard(html);
if (twitterValidation.issues.length === 0) {
log.success('All required Twitter Card tags present');
} else {
twitterValidation.issues.forEach(issue => log.error(` ${issue}`));
}
if (twitterValidation.warnings.length > 0) {
twitterValidation.warnings.forEach(warning => log.warning(` ${warning}`));
}
return {
schemas: schemas.length,
issues: ogValidation.issues.length + twitterValidation.issues.length,
warnings: ogValidation.warnings.length + twitterValidation.warnings.length,
};
} catch (error) {
log.error(`Failed to validate ${pageName}: ${error.message}`);
return { schemas: 0, issues: 1, warnings: 0 };
}
}
/**
* Main execution
*/
async function main() {
console.log('\n╔════════════════════════════════════════════════════════╗');
console.log('║ Schema Validation for WorkRoot IT Solutions ║');
console.log('╚════════════════════════════════════════════════════════╝\n');
const distDir = path.join(__dirname, '..', 'dist');
if (!fs.existsSync(distDir)) {
log.error('Build directory not found. Please run "npm run build" first.');
process.exit(1);
}
// Pages to validate
const pages = [
{ path: path.join(distDir, 'index.html'), name: 'Homepage' },
{ path: path.join(distDir, 'about', 'index.html'), name: 'About' },
{ path: path.join(distDir, 'services', 'index.html'), name: 'Services' },
{ path: path.join(distDir, 'contact', 'index.html'), name: 'Contact' },
{ path: path.join(distDir, 'blog', 'index.html'), name: 'Blog Index' },
];
// Find a blog post to validate
const blogDir = path.join(distDir, 'blog');
if (fs.existsSync(blogDir)) {
const blogPosts = fs.readdirSync(blogDir)
.filter(file => fs.statSync(path.join(blogDir, file)).isDirectory());
if (blogPosts.length > 0) {
pages.push({
path: path.join(blogDir, blogPosts[0], 'index.html'),
name: `Blog Post (${blogPosts[0]})`
});
}
}
let totalSchemas = 0;
let totalIssues = 0;
let totalWarnings = 0;
for (const page of pages) {
if (fs.existsSync(page.path)) {
const result = await validatePage(page.path, page.name);
totalSchemas += result.schemas;
totalIssues += result.issues;
totalWarnings += result.warnings;
} else {
log.warning(`Page not found: ${page.name}`);
}
}
// Summary
log.section('Validation Summary');
console.log(`Total schemas found: ${COLORS.cyan}${totalSchemas}${COLORS.reset}`);
console.log(`Total issues: ${totalIssues > 0 ? COLORS.red : COLORS.green}${totalIssues}${COLORS.reset}`);
console.log(`Total warnings: ${totalWarnings > 0 ? COLORS.yellow : COLORS.green}${totalWarnings}${COLORS.reset}`);
if (totalIssues === 0) {
log.success('\nAll validations passed! 🎉');
} else {
log.error('\nPlease fix the issues above before deployment.');
}
console.log('\n');
process.exit(totalIssues > 0 ? 1 : 0);
}
main();