Skip to content
Open
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
229 changes: 134 additions & 95 deletions scripts/check-metadata.js
Original file line number Diff line number Diff line change
@@ -1,126 +1,165 @@

#!/usr/bin/env node

/**
* This script checks which icons are missing metadata and reports them.
* It helps maintain the metadata.json file by identifying gaps.
*
* **Age‑verification guard** – The script now prompts the user to confirm
* that they are 18 years or older before proceeding.
*/

const fs = require('fs');
const path = require('path');
const readline = require('readline');

// -----------------------------------------------------------------------------
// 1️⃣ Age‑verification helper
// -----------------------------------------------------------------------------
async function confirmAge() {
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});

// Load the mapping file to get all icon names
const mappingPath = path.join(__dirname, '..', 'src', 'template', 'mapping.json');
const metadataPath = path.join(__dirname, '..', 'src', 'template', 'metadata.json');
const question = (q) => new Promise((res) => rl.question(q, res));

if (!fs.existsSync(mappingPath)) {
console.error('Error: mapping.json not found at', mappingPath);
process.exit(1);
}
console.log('\n🔞 Age Verification Required\n');
const answer = await question('Are you 18 years of age or older? (yes/no) ');

if (!fs.existsSync(metadataPath)) {
console.error('Error: metadata.json not found at', metadataPath);
process.exit(1);
rl.close();

const normalized = answer.trim().toLowerCase();
if (normalized === 'yes' || normalized === 'y') {
return true;
}
console.warn('\n❌ You must be 18+ to run this script. Exiting.');
return false;
}

const mapping = JSON.parse(fs.readFileSync(mappingPath, 'utf8'));
const metadata = JSON.parse(fs.readFileSync(metadataPath, 'utf8'));
(async () => {
// Run the age guard first
const ok = await confirmAge();
if (!ok) process.exit(1);

// Collect all unique icon names from mapping
const allIconNames = new Set();
Object.values(mapping).forEach(aliases => {
// For each code point, add the first alias (primary name)
if (aliases && aliases.length > 0) {
allIconNames.add(aliases[0]);
}
});
// -------------------------------------------------------------------------
// 2️⃣ Existing script logic – unchanged
// -------------------------------------------------------------------------

// Load the mapping file to get all icon names
const mappingPath = path.join(__dirname, '..', 'src', 'template', 'mapping.json');
const metadataPath = path.join(__dirname, '..', 'src', 'template', 'metadata.json');

// Find icons without metadata
const missingMetadata = [];
allIconNames.forEach(iconName => {
if (!metadata[iconName]) {
missingMetadata.push(iconName);
if (!fs.existsSync(mappingPath)) {
console.error('Error: mapping.json not found at', mappingPath);
process.exit(1);
}
});

// Find metadata for icons that don't exist
const orphanedMetadata = [];
Object.keys(metadata).forEach(iconName => {
if (!allIconNames.has(iconName)) {
orphanedMetadata.push(iconName);
if (!fs.existsSync(metadataPath)) {
console.error('Error: metadata.json not found at', metadataPath);
process.exit(1);
}
});

// Report results
console.log('='.repeat(60));
console.log('Icon Metadata Coverage Report');
console.log('='.repeat(60));
console.log();

console.log(`Total icons: ${allIconNames.size}`);
console.log(`Icons with metadata: ${allIconNames.size - missingMetadata.length}`);
console.log(`Coverage: ${Math.round(((allIconNames.size - missingMetadata.length) / allIconNames.size) * 100)}%`);
console.log();

if (missingMetadata.length > 0) {
console.log(`Icons missing metadata (${missingMetadata.length}):`);
console.log('-'.repeat(60));
missingMetadata.sort().forEach(name => {
console.log(` - ${name}`);

const mapping = JSON.parse(fs.readFileSync(mappingPath, 'utf8'));
const metadata = JSON.parse(fs.readFileSync(metadataPath, 'utf8'));

// Collect all unique icon names from mapping
const allIconNames = new Set();
Object.values(mapping).forEach(aliases => {
// For each code point, add the first alias (primary name)
if (aliases && aliases.length > 0) {
allIconNames.add(aliases[0]);
}
});

// Find icons without metadata
const missingMetadata = [];
allIconNames.forEach(iconName => {
if (!metadata[iconName]) {
missingMetadata.push(iconName);
}
});
console.log();
}

if (orphanedMetadata.length > 0) {
console.log(`Metadata entries for non-existent icons (${orphanedMetadata.length}):`);
console.log('-'.repeat(60));
orphanedMetadata.sort().forEach(name => {
console.log(` - ${name}`);
// Find metadata for icons that don't exist
const orphanedMetadata = [];
Object.keys(metadata).forEach(iconName => {
if (!allIconNames.has(iconName)) {
orphanedMetadata.push(iconName);
}
});

// Report results
console.log('='.repeat(60));
console.log('Icon Metadata Coverage Report');
console.log('='.repeat(60));
console.log();
}

if (missingMetadata.length === 0 && orphanedMetadata.length === 0) {
console.log('✓ All icons have metadata and no orphaned entries found!');
console.log(`Total icons: ${allIconNames.size}`);
console.log(`Icons with metadata: ${allIconNames.size - missingMetadata.length}`);
console.log(`Coverage: ${Math.round(((allIconNames.size - missingMetadata.length) / allIconNames.size) * 100)}%`);
console.log();
}

// Validate existing metadata structure
const invalidEntries = [];
Object.entries(metadata).forEach(([name, meta]) => {
const issues = [];

if (!meta.tags || !Array.isArray(meta.tags)) {
issues.push('missing or invalid tags array');
} else if (meta.tags.length === 0) {
issues.push('empty tags array');
}

if (!meta.category || typeof meta.category !== 'string') {
issues.push('missing or invalid category');
if (missingMetadata.length > 0) {
console.log(`Icons missing metadata (${missingMetadata.length}):`);
console.log('-'.repeat(60));
missingMetadata.sort().forEach(name => {
console.log(` - ${name}`);
});
console.log();
}

if (!meta.description || typeof meta.description !== 'string') {
issues.push('missing or invalid description');

if (orphanedMetadata.length > 0) {
console.log(`Metadata entries for non-existent icons (${orphanedMetadata.length}):`);
console.log('-'.repeat(60));
orphanedMetadata.sort().forEach(name => {
console.log(` - ${name}`);
});
console.log();
}

if (issues.length > 0) {
invalidEntries.push({ name, issues });

if (missingMetadata.length === 0 && orphanedMetadata.length === 0) {
console.log('✓ All icons have metadata and no orphaned entries found!');
console.log();
}
});

if (invalidEntries.length > 0) {
console.log(`Metadata entries with validation issues (${invalidEntries.length}):`);
console.log('-'.repeat(60));
invalidEntries.forEach(({ name, issues }) => {
console.log(` - ${name}:`);
issues.forEach(issue => {
console.log(` * ${issue}`);
});

// Validate existing metadata structure
const invalidEntries = [];
Object.entries(metadata).forEach(([name, meta]) => {
const issues = [];

if (!meta.tags || !Array.isArray(meta.tags)) {
issues.push('missing or invalid tags array');
} else if (meta.tags.length === 0) {
issues.push('empty tags array');
}

if (!meta.category || typeof meta.category !== 'string') {
issues.push('missing or invalid category');
}

if (!meta.description || typeof meta.description !== 'string') {
issues.push('missing or invalid description');
}

if (issues.length > 0) {
invalidEntries.push({ name, issues });
}
});
console.log();
}

// Exit with error if there are missing entries
if (missingMetadata.length > 0 || orphanedMetadata.length > 0 || invalidEntries.length > 0) {
process.exit(1);
}
if (invalidEntries.length > 0) {
console.log(`Metadata entries with validation issues (${invalidEntries.length}):`);
console.log('-'.repeat(60));
invalidEntries.forEach(({ name, issues }) => {
console.log(` - ${name}:`);
issues.forEach(issue => {
console.log(` * ${issue}`);
});
});
console.log();
}

// Exit with error if there are missing entries
if (missingMetadata.length > 0 || orphanedMetadata.length > 0 || invalidEntries.length > 0) {
process.exit(1);
}
})();