Files
NewTube/server/tests/generate-provider-doc.mjs
T
bruno 665a0f0ebd
CI / build-and-test (push) Successful in 14m43s
feat(providers): phases 7.3/7.4/7.6/8.1 — provenance, health, NDJSON, contrat unique
7.3: capturedAt/source au registre + 6 adaptateurs + module provenance.ts + ?debug=1 (search-transport.mjs). 7.4: ProviderHealthService + badge source degradee. 7.6: squelettes par provider + snapshots progressifs + transport NDJSON /api/search. 8.1: ProviderAdapter unifie (search enveloppe + channelContent/channelMeta/capabilities) via getProviderAdapter + test de contrat offline.
2026-09-30 07:57:11 -04:00

104 lines
4.2 KiB
JavaScript

/**
* Phase 8.5 — documentation GÉNÉRÉE depuis les fixtures gelées.
*
* Produit la matrice « champ ↔ provider » à partir de ce que les adaptateurs
* émettent RÉELLEMENT, et l'injecte entre deux marqueurs dans
* `docs/ingestion-catalogue-video-par-fournisseur.md`.
*
* Raison d'être : une matrice écrite à la main devient fausse au premier
* adaptateur modifié, et personne ne le remarque — c'est le pire sort pour une
* doc de référence. Générée, elle ne peut qu'être à jour ou absente.
*
* La section générée est donc explicitement marquée comme telle, et `npm run
* test:shapes` échoue si elle n'est plus synchronisée (dérive = doc fausse).
*/
import fs from 'node:fs';
import path from 'node:path';
const ROOT = path.resolve(import.meta.dirname, '../..');
const FIXTURES = path.join(ROOT, 'server/tests/fixtures/provider-suggestions.json');
const DOC = path.join(ROOT, 'docs/ingestion-catalogue-video-par-fournisseur.md');
const BEGIN = '<!-- GENERATED:provider-matrix (npm run doc:providers) -->';
const END = '<!-- /GENERATED:provider-matrix -->';
const data = JSON.parse(fs.readFileSync(FIXTURES, 'utf8'));
const ids = Object.keys(data.providers);
const labels = { yt: 'YouTube', dm: 'Dailymotion', tw: 'Twitch', pt: 'PeerTube', od: 'Odysee', ru: 'Rumble' };
// Champs d'intérêt métier, dans un ordre de lecture stable (le tri
// alphabétique de `Object.keys` n'est pas l'ordre du contrat).
const FIELDS = [
['duration', 'durée', 'secondes'],
['views', 'vues', 'nombre'],
['likes', 'likes', 'nombre'],
['publishedAt', 'publication', 'date ISO'],
['thumbnail', 'vignette', 'URL'],
['uploaderName', 'chaîne', 'texte'],
['channelRef', 'identité chaîne', 'scheme + value'],
['type', 'type', 'video / live / short'],
['kind', 'kind', 'vod / live / clip / channel'],
['isLive', 'direct', 'booléen'],
['width', 'largeur', 'px'],
['height', 'hauteur', 'px'],
['language', 'langue', 'code'],
];
const mark = (present) => (present ? 'x' : '·');
const lines = [];
lines.push(BEGIN);
lines.push('');
lines.push(`> Section **générée** par \`npm run doc:providers\` depuis \`server/tests/fixtures/provider-suggestions.json\``);
lines.push(`> (gel du ${data._recordedAt.slice(0, 10)}, requête \`${data._query}\`). Ne pas éditer à la main.`);
lines.push('');
lines.push(`Legende : \`x\` = émis dans le gel, \`·\` = absent (donnée inconnue, donc \`undefined\` côté front).`);
lines.push('');
const head = ['Champ', 'Type', ...ids.map((id) => labels[id] || id)];
lines.push(`| ${head.join(' | ')} |`);
lines.push(`|${head.map(() => '---').join('|')}|`);
for (const [field, label, type] of FIELDS) {
const cells = ids.map((id) => {
const items = data.providers[id]?.items || [];
// Sur un provider sans fixture, on ne peut rien affirmer : ni x ni ·.
if (items.length === 0) return '?';
return mark(items.some((it) => it[field] !== undefined));
});
lines.push(`| \`${field}\` (${label}) | ${type} | ${cells.join(' | ')} |`);
}
lines.push('');
lines.push('Couverture du gel :');
lines.push('');
for (const id of ids) {
const entry = data.providers[id];
const n = entry.items.length;
lines.push(n
? `- **${labels[id] || id}** : ${n} item(s) vérifié(s).`
: `- **${labels[id] || id}** : *non couvert* — ${entry.note || 'aucune fixture'}.`);
}
lines.push('');
lines.push('(`?` = provider sans fixture au gel : la matrice ne prétend rien sur lui.)');
lines.push('');
lines.push(END);
const block = lines.join('\n');
const doc = fs.readFileSync(DOC, 'utf8');
if (doc.includes(BEGIN)) {
const from = doc.indexOf(BEGIN);
const to = doc.indexOf(END) + END.length;
fs.writeFileSync(DOC, doc.slice(0, from) + block + doc.slice(to));
console.log('matrice régénérée (remplacement)');
} else {
// Ancre : juste après le titre de la section correspondente.
const anchor = '## 4. Catalogue';
const at = doc.indexOf(anchor);
if (at < 0) throw new Error(`ancre « ${anchor} » introuvable dans ${path.basename(DOC)}`);
const insertAt = doc.indexOf('\n', at) + 1;
fs.writeFileSync(DOC, `${doc.slice(0, insertAt)}\n${block}\n${doc.slice(insertAt)}`);
console.log('matrice générée (insertion)');
}
console.log(`→ ${path.relative(process.cwd(), DOC)}`);