Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions src/routes/ethereum/entities.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,13 @@ export const Route = createFileRoute('/ethereum/entities')({
content:
'Explore Ethereum validator entities. View entity attestation performance, block proposals, and activity.',
},
{ property: 'og:image', content: '/images/ethereum/entities.png' },
{ name: 'twitter:url', content: `${import.meta.env.VITE_BASE_URL}/ethereum/entities` },
{ name: 'twitter:title', content: `Entities | ${import.meta.env.VITE_BASE_TITLE}` },
{
name: 'twitter:description',
content:
'Explore Ethereum validator entities. View entity attestation performance, block proposals, and activity.',
},
{ name: 'twitter:image', content: '/images/ethereum/entities.png' },
],
}),
});
2 changes: 0 additions & 2 deletions src/routes/ethereum/entities/$entity.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,15 +29,13 @@ export const Route = createFileRoute('/ethereum/entities/$entity')({
content:
'Detailed analysis of a validator entity including attestation performance, block proposals, and historical activity trends.',
},
{ property: 'og:image', content: '/images/ethereum/entities.png' },
{ name: 'twitter:url', content: `${import.meta.env.VITE_BASE_URL}/ethereum/entities/${ctx.params.entity}` },
{ name: 'twitter:title', content: `${ctx.params.entity} | ${import.meta.env.VITE_BASE_TITLE}` },
{
name: 'twitter:description',
content:
'Detailed analysis of a validator entity including attestation performance, block proposals, and historical activity trends.',
},
{ name: 'twitter:image', content: '/images/ethereum/entities.png' },
],
}),
});
2 changes: 0 additions & 2 deletions src/routes/ethereum/entities/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,13 @@ export const Route = createFileRoute('/ethereum/entities/')({
content:
'Explore Ethereum validator entities. View entity attestation performance, block proposals, and activity.',
},
{ property: 'og:image', content: '/images/ethereum/entities.png' },
{ name: 'twitter:url', content: `${import.meta.env.VITE_BASE_URL}/ethereum/entities` },
{ name: 'twitter:title', content: `Entities | ${import.meta.env.VITE_BASE_TITLE}` },
{
name: 'twitter:description',
content:
'Explore Ethereum validator entities. View entity attestation performance, block proposals, and activity.',
},
{ name: 'twitter:image', content: '/images/ethereum/entities.png' },
],
}),
});
8 changes: 0 additions & 8 deletions src/routes/ethereum/forks.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,20 +24,12 @@ export const Route = createFileRoute('/ethereum/forks')({
property: 'og:description',
content: 'View Ethereum consensus layer fork history and upcoming network upgrades',
},
{
property: 'og:image',
content: '/images/ethereum/forks.png',
},
{ name: 'twitter:url', content: `${import.meta.env.VITE_BASE_URL}/ethereum/forks` },
{ name: 'twitter:title', content: `Forks | ${import.meta.env.VITE_BASE_TITLE}` },
{
name: 'twitter:description',
content: 'View Ethereum consensus layer fork history and upcoming network upgrades',
},
{
name: 'twitter:image',
content: '/images/ethereum/forks.png',
},
],
}),
});
8 changes: 0 additions & 8 deletions src/routes/ethereum/forks/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,20 +22,12 @@ export const Route = createFileRoute('/ethereum/forks/')({
property: 'og:description',
content: 'View Ethereum consensus layer fork history and upcoming network upgrades',
},
{
property: 'og:image',
content: '/images/ethereum/forks.png',
},
{ name: 'twitter:url', content: `${import.meta.env.VITE_BASE_URL}/ethereum/forks` },
{ name: 'twitter:title', content: `Forks | ${import.meta.env.VITE_BASE_TITLE}` },
{
name: 'twitter:description',
content: 'View Ethereum consensus layer fork history and upcoming network upgrades',
},
{
name: 'twitter:image',
content: '/images/ethereum/forks.png',
},
],
}),
});
94 changes: 94 additions & 0 deletions vite-plugin-validate-route-images.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import { promises as fs } from 'node:fs';
import { join } from 'node:path';
import type { Plugin } from 'vite';

/**
* Recursively find all .tsx files in a directory
*/
async function findTsxFiles(dir: string): Promise<string[]> {
const files: string[] = [];
const entries = await fs.readdir(dir, { withFileTypes: true });

for (const entry of entries) {
const fullPath = join(dir, entry.name);
if (entry.isDirectory()) {
files.push(...(await findTsxFiles(fullPath)));
} else if (entry.isFile() && entry.name.endsWith('.tsx')) {
files.push(fullPath);
}
}

return files;
}

/**
* Vite plugin to validate that image files referenced in route meta tags exist.
*
* This plugin scans all route files for og:image and twitter:image meta tags,
* extracts the image paths, and verifies that the corresponding files exist
* in the public directory. If any images are missing, the build fails with
* a clear error message.
*/
export function validateRouteImages(): Plugin {
return {
name: 'validate-route-images',
async buildStart() {
const publicDir = join(process.cwd(), 'public');
const routesDir = join(process.cwd(), 'src', 'routes');

// Find all route files
const routeFiles = await findTsxFiles(routesDir);

const missingImagesByPath = new Map<string, Set<string>>();
const imageRegex =
/(?:property|name):\s*['"](?:og:image|twitter:image)['"]\s*,\s*content:\s*['"](\/images\/[^'"]+)['"]/g;

for (const routeFile of routeFiles) {
const content = await fs.readFile(routeFile, 'utf-8');

// Find all image references in this file
const imagesInFile = new Set<string>();
let match;
while ((match = imageRegex.exec(content)) !== null) {
const imagePath = match[1]; // e.g., '/images/ethereum/forks.png'
imagesInFile.add(imagePath);
}

// Check each unique image in this file
for (const imagePath of imagesInFile) {
const fullImagePath = join(publicDir, imagePath);

try {
await fs.access(fullImagePath);
} catch {
// Image doesn't exist
const relativeRoutePath = routeFile.replace(process.cwd() + '/', '');
if (!missingImagesByPath.has(imagePath)) {
missingImagesByPath.set(imagePath, new Set());
}
missingImagesByPath.get(imagePath)!.add(relativeRoutePath);
}
}
}

if (missingImagesByPath.size > 0) {
const errorLines = ['\n❌ Route image validation failed!\n'];
errorLines.push('The following images are referenced but do not exist:\n');

for (const [imagePath, files] of missingImagesByPath) {
errorLines.push(`\n Missing: ${imagePath}`);
errorLines.push(' Referenced in:');
for (const file of files) {
errorLines.push(` • ${file}`);
}
}

errorLines.push('\nPlease create the missing images in the public directory or remove the references.\n');

throw new Error(errorLines.join('\n'));
}

console.log('✅ All route images validated successfully');
},
};
}
2 changes: 2 additions & 0 deletions vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { tanstackRouter } from '@tanstack/router-plugin/vite';
import { visualizer } from 'rollup-plugin-visualizer';
import path from 'path';
import { generateHeadPlugin } from './vite-plugins/generate-head-plugin';
import { validateRouteImages } from './vite-plugin-validate-route-images';

// https://vite.dev/config/
export default defineConfig({
Expand All @@ -14,6 +15,7 @@ export default defineConfig({
'import.meta.env.VITE_BASE_URL': JSON.stringify('https://lab2.ethpandaops.io'),
},
plugins: [
validateRouteImages(),
tanstackRouter({
routesDirectory: './src/routes',
generatedRouteTree: './src/routeTree.gen.ts',
Expand Down