#!/usr/bin/env node /** * build-credits.mjs — génère CREDITS.md depuis les données. * * Le brief §3 exige un CREDITS.md « généré depuis les données ». Un fichier de crédits * tenu à la main se désynchronise le jour où l'on remplace une photo, et une attribution * fausse est une violation de licence. Ce fichier n'est donc jamais édité : il se rejoue. * * Les médias sous clause non commerciale sont isolés dans leur propre section, comme * l'ADR 0001 l'impose. */ import { readFile, readdir, writeFile } from 'node:fs/promises'; import { fileURLToPath } from 'node:url'; import { dirname, join } from 'node:path'; const ROOT = join(dirname(fileURLToPath(import.meta.url)), '..', '..'); const DATA = join(ROOT, 'src', 'lib', 'data', 'fruits'); const LIBELLE = { 'CC0-1.0': 'CC0 1.0 (domaine public volontaire)', PD: 'domaine public', 'CC-BY-2.0': 'CC BY 2.0', 'CC-BY-3.0': 'CC BY 3.0', 'CC-BY-4.0': 'CC BY 4.0', 'CC-BY-SA-2.0': 'CC BY-SA 2.0', 'CC-BY-SA-2.5': 'CC BY-SA 2.5', 'CC-BY-SA-3.0': 'CC BY-SA 3.0', 'CC-BY-SA-4.0': 'CC BY-SA 4.0', 'CC-BY-NC-4.0': 'CC BY-NC 4.0', 'CC-BY-NC-SA-4.0': 'CC BY-NC-SA 4.0', 'OKI-CC-BY-SA-4.0': 'ORGANISATION KA INTERNATIONALE, CC BY-SA 4.0' }; const echapper = (s) => String(s ?? '—').replace(/\|/g, '\\|').replace(/\n/g, ' '); async function main() { const fichiers = (await readdir(DATA)).filter((f) => f.endsWith('.json')).sort(); const entrees = []; for (const f of fichiers) entrees.push(JSON.parse(await readFile(join(DATA, f), 'utf8'))); const tous = entrees.flatMap((e) => e.medias.map((m) => ({ ...m, entree: e }))); const nc = tous.filter((m) => m.usage_non_commercial); const sa = tous.filter((m) => m.partage_identique); const l = [ '# Crédits — KI FWI SA YÉ ?', '', '> **Fichier généré.** Ne pas éditer à la main : `npm run build:credits`.', `> Dernière génération : ${new Date().toISOString().slice(0, 10)}.`, '', '## Ce que couvre ce fichier', '', 'Chaque média embarqué dans le jeu ou dans l’encyclopédie est listé ci-dessous avec son', 'auteur, sa licence et le lien vers sa page de description d’origine. Rien n’est', '« libre de droit » : chaque ligne est une obligation contractuelle.', '', 'Le code du projet est sous **AGPL-3.0-or-later** (`LICENSE`). Les textes rédigés par', 'ORGANISATION KA INTERNATIONALE sont sous **CC BY-SA 4.0** (`LICENSE-CONTENT`). Les médias', 'tiers restent sous leur licence propre : le jeu est une **œuvre collective**, pas une', 'œuvre dérivée — voir `docs/adr/0001-licences.md`.', '', `## Chiffres`, '', `- ${entrees.length} entrées · ${tous.length} médias`, `- ${tous.filter((m) => m.valide).length} médias validés par un humain`, `- ${sa.length} médias en partage à l’identique (CC BY-SA) — **aucun compositing autorisé**`, `- ${nc.length} médias sous clause non commerciale (CC BY-NC)`, '' ]; if (nc.length) { l.push( '## ⚠ Médias sous clause non commerciale', '', 'La présence de ces médias engage le projet à ne **jamais** être distribué à titre', 'commercial — ni vente, ni version payante, ni licence à un éditeur. Décision prise et', 'documentée dans `docs/adr/0001-licences.md`. Retirer ces médias est la seule façon de', 'revenir en arrière.', '', '| entrée | média | auteur | licence | source |', '|---|---|---|---|---|' ); for (const m of nc) l.push( `| \`${m.entree.id}\` | ${echapper(m.id)} | ${echapper(m.auteur)} | ${LIBELLE[m.licence] ?? m.licence} | [page](${m.url_description}) |` ); l.push(''); } l.push('## Médias par entrée', ''); for (const e of entrees) { l.push(`### ${e.noms.gcf} — *${e.taxon.scientifique}* (\`${e.id}\`)`, ''); l.push( `Taxonomie vérifiée le ${e.taxon.verifie_le} contre [Wikidata ${e.taxon.wikidata}](https://www.wikidata.org/wiki/${e.taxon.wikidata})`, `et [GBIF ${e.taxon.gbif}](https://www.gbif.org/species/${e.taxon.gbif}).`, '' ); if (!e.medias.length) { l.push('_Aucun média._', ''); continue; } l.push('| média | vue | auteur | licence | validé | page d’origine |'); l.push('|---|---|---|---|---|---|'); for (const m of e.medias) l.push( `| ${echapper(m.id)} | ${m.vue ?? '—'} | ${echapper(m.auteur)} | ${LIBELLE[m.licence] ?? m.licence} | ${m.valide ? `oui (${echapper(m.valide_par)})` : 'non'} | [voir](${m.url_description}) |` ); l.push(''); } l.push( '## Sources factuelles', '', '- **Wikidata** — identifiants et taxonomie (CC0). https://www.wikidata.org', '- **GBIF** — noms scientifiques acceptés et clés d’usage. https://www.gbif.org', '- **Wikimedia Commons** — photographies. https://commons.wikimedia.org', '', 'Voir `SOURCES.md` pour le détail des régimes de licence et `docs/audit-sources.md`', 'pour l’audit complet, TRAMIL et CIRAD compris.', '' ); await writeFile(join(ROOT, 'CREDITS.md'), l.join('\n')); console.log(`✔ CREDITS.md — ${entrees.length} entrées, ${tous.length} médias, ${nc.length} sous clause NC.`); } main().catch((e) => { console.error('✘', e.stack ?? e.message); process.exit(1); });