/** * Compara el catálogo de la base con el de los archivos, campo a campo. * * npm run db:comparar * * Es la red de la fase 4. Cambiar de dónde salen los datos de veinte rutas sin * poder comprobar que salen iguales sería confiar en que las pruebas de * interfaz noten una diferencia de un año o de un porcentaje de autoría, y no * la notarían: pintan lo que les den. * * Lo que se compara es el objeto entero de cada canción, álbum y estilo, con * las mismas claves y ordenado igual. Las diferencias se listan una a una con * su ruta —`dorina.registro.reparto[0].porcentaje`— porque un «no coinciden» * sin más no sirve para arreglar nada. */ import fs from 'node:fs'; import path from 'node:path'; import { load as leerYaml } from 'js-yaml'; import { parsearAlbum, parsearCancion, parsearEstilo } from '$lib/content/schema'; import { extraerLineas } from '$lib/content/extracto'; import { leerCatalogo } from '$lib/server/catalogo'; /* * Los archivos se leen a mano y no por `$lib/content`, que usa * `import.meta.glob`: eso lo resuelve Vite al construir y en Node no existe. * Los analizadores sí son los mismos, que es lo que hace que la comparación * signifique algo. */ const RAIZ = 'src/content'; const SALTO = String.fromCharCode(10); function partir(texto) { const lineas = texto.split(new RegExp(String.raw`\r?\n`)); if (lineas[0]?.trim() !== '---') return { datos: {}, cuerpo: texto.trim() }; const cierre = lineas.indexOf('---', 1); if (cierre === -1) return { datos: {}, cuerpo: texto.trim() }; return { datos: leerYaml(lineas.slice(1, cierre).join(SALTO)) ?? {}, cuerpo: lineas .slice(cierre + 1) .join(SALTO) .trim() }; } function leerCarpeta(carpeta, parser) { const dir = path.join(RAIZ, carpeta); if (!fs.existsSync(dir)) return []; return fs .readdirSync(dir) .filter((n) => n.endsWith('.md')) .sort() .map((nombre) => { const ruta = path.join(dir, nombre); const { datos, cuerpo } = partir(fs.readFileSync(ruta, 'utf8')); const slug = nombre.replace(/\.md$/, ''); const parseado = parser(datos, slug, ruta); // El extracto lo inyecta un preprocesador al construir, no el parser. if (carpeta === 'canciones') parseado.extracto = extraerLineas(cuerpo); return parseado; }); } const deArchivo = leerCarpeta('canciones', parsearCancion); const albumesArchivo = leerCarpeta('albumes', parsearAlbum); const estilosArchivo = leerCarpeta('estilos', parsearEstilo); /** Recorre dos objetos a la vez y devuelve dónde difieren. */ function diferencias(a, b, ruta = '') { const fuera = []; if (a === b) return fuera; if (Array.isArray(a) && Array.isArray(b)) { if (a.length !== b.length) { fuera.push(`${ruta}: archivo ${a.length} elementos, base ${b.length}`); } for (let i = 0; i < Math.max(a.length, b.length); i++) { fuera.push(...diferencias(a[i], b[i], `${ruta}[${i}]`)); } return fuera; } if (a && b && typeof a === 'object' && typeof b === 'object') { for (const clave of new Set([...Object.keys(a), ...Object.keys(b)])) { fuera.push(...diferencias(a[clave], b[clave], ruta ? `${ruta}.${clave}` : clave)); } return fuera; } // `undefined` y ausente son lo mismo para este sitio: el tipo los declara // opcionales y la plantilla los trata igual. if ((a ?? undefined) === (b ?? undefined)) return fuera; fuera.push(`${ruta}: archivo ${JSON.stringify(a)} · base ${JSON.stringify(b)}`); return fuera; } const base = await leerCatalogo(); const grupos = [ ['canciones', deArchivo, base.canciones], ['álbumes', albumesArchivo, base.albumes], ['estilos', estilosArchivo, base.estilos] ]; let problemas = 0; for (const [nombre, archivo, deBase] of grupos) { console.log(`\n${nombre.toUpperCase()} · archivo ${archivo.length} · base ${deBase.length}`); const porSlug = new Map(deBase.map((x) => [x.slug, x])); for (const uno of archivo) { const otro = porSlug.get(uno.slug); if (!otro) { console.log(` ✗ ${uno.slug}: no está en la base`); problemas++; continue; } porSlug.delete(uno.slug); const fuera = diferencias(uno, otro, uno.slug); if (fuera.length === 0) continue; problemas += fuera.length; console.log(` ✗ ${uno.slug}`); for (const d of fuera) console.log(` ${d}`); } for (const sobrante of porSlug.keys()) { console.log(` · ${sobrante}: solo en la base (no es un fallo si se añadió después)`); } } console.log( problemas === 0 ? '\nIdénticos. La base puede sustituir a los archivos sin que cambie nada.' : `\n${problemas} diferencias. Cada una es algo que la web enseñaría distinto.` ); process.exit(problemas === 0 ? 0 : 1);