Files
proxmox/token-lists/scripts/validate-logos.js

136 lines
4.0 KiB
JavaScript
Raw Permalink Normal View History

#!/usr/bin/env node
/**
* Logo URL Validator
* Validates that all logoURI URLs are accessible and return image content
*/
import { readFileSync } from 'fs';
import { fileURLToPath } from 'url';
import { dirname, resolve } from 'path';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const MAX_LOGO_SIZE = 500 * 1024; // 500KB
const IMAGE_MIME_TYPES = ['image/png', 'image/jpeg', 'image/jpg', 'image/svg+xml', 'image/webp', 'image/gif'];
async function validateLogo(logoURI, tokenInfo) {
const issues = [];
// Check protocol
if (!logoURI.startsWith('https://') && !logoURI.startsWith('ipfs://')) {
issues.push(`URL should use HTTPS or IPFS (got: ${logoURI.substring(0, 20)}...)`);
}
// For HTTPS URLs, validate accessibility
if (logoURI.startsWith('https://')) {
try {
const response = await fetch(logoURI, { method: 'HEAD' });
if (!response.ok) {
issues.push(`HTTP ${response.status}: ${response.statusText}`);
} else {
const contentType = response.headers.get('content-type');
const contentLength = response.headers.get('content-length');
if (contentType && !IMAGE_MIME_TYPES.some(mime => contentType.includes(mime))) {
issues.push(`Invalid Content-Type: ${contentType} (expected image/*)`);
}
if (contentLength && parseInt(contentLength) > MAX_LOGO_SIZE) {
issues.push(`Logo too large: ${(parseInt(contentLength) / 1024).toFixed(2)}KB (max ${MAX_LOGO_SIZE / 1024}KB)`);
}
}
} catch (error) {
issues.push(`Failed to fetch: ${error.message}`);
}
} else if (logoURI.startsWith('ipfs://')) {
// IPFS URLs are valid but we can't easily validate them
// Just check format
if (!logoURI.match(/^ipfs:\/\/[a-zA-Z0-9]+/)) {
issues.push('Invalid IPFS URL format');
}
}
return issues;
}
async function validateLogos(filePath) {
console.log(`\n🖼️ Validating logos in: ${filePath}\n`);
// Read token list file
let tokenList;
try {
const fileContent = readFileSync(filePath, 'utf-8');
tokenList = JSON.parse(fileContent);
} catch (error) {
console.error('❌ Error reading or parsing token list file:');
console.error(` ${error.message}`);
process.exit(1);
}
const results = [];
let totalIssues = 0;
// Validate top-level logoURI
if (tokenList.logoURI) {
console.log('Validating list logoURI...');
const issues = await validateLogo(tokenList.logoURI, 'List');
if (issues.length > 0) {
results.push({ type: 'list', uri: tokenList.logoURI, issues });
totalIssues += issues.length;
}
}
// Validate token logos
if (tokenList.tokens && Array.isArray(tokenList.tokens)) {
for (const [index, token] of tokenList.tokens.entries()) {
if (token.logoURI) {
const tokenInfo = `${token.symbol || token.name} (Token[${index}])`;
const issues = await validateLogo(token.logoURI, tokenInfo);
if (issues.length > 0) {
results.push({ type: 'token', token: tokenInfo, uri: token.logoURI, issues });
totalIssues += issues.length;
}
}
}
}
// Report results
if (totalIssues === 0) {
console.log('✅ All logos are valid!\n');
return 0;
}
console.log(`Found ${totalIssues} logo issue(s):\n`);
results.forEach(result => {
if (result.type === 'list') {
console.log(`❌ List logoURI: ${result.uri}`);
} else {
console.log(`${result.token}: ${result.uri}`);
}
result.issues.forEach(issue => {
console.log(` - ${issue}`);
});
console.log('');
});
return 1;
}
// Main
const filePath = process.argv[2] || resolve(__dirname, '../lists/dbis-138.tokenlist.json');
if (!filePath) {
console.error('Usage: node validate-logos.js [path/to/token-list.json]');
process.exit(1);
}
validateLogos(filePath).then(exitCode => {
process.exit(exitCode);
}).catch(error => {
console.error('Unexpected error:', error);
process.exit(1);
});