Repository navigation
Expand file tree
/
Copy pathverifier.mjs
More file actions
155 lines (138 loc) · 6.57 KB
/
Copy pathverifier.mjs
File metadata and controls
155 lines (138 loc) · 6.57 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
import { readFileSync } from 'node:fs'
import { fileURLToPath } from 'node:url'
import { dirname, join, resolve } from 'node:path'
/**
* Vérifie le profil public de l'organisation.
*
* **Ce fichier existe à cause d'un défaut réel, signalé par l'utilisateur :
* Lumika n'apparaissait nulle part.** Le profil annonçait « Trois applications
* de bureau pour trois métiers » et en listait trois, alors que la maison en
* compte quatre depuis le 12 août 2026. La quatrième était publiée, avec son
* dépôt, son site et sa release — et la première page que voit un visiteur de
* l'organisation l'ignorait.
*
* **Rien ne pouvait le voir.** Ce dépôt n'avait aucune vérification, et le
* README d'à côté demandait pourtant, en toutes lettres, de ne pas laisser les
* deux langues diverger. Une consigne écrite n'est pas un garde-fou : personne
* ne la relit au moment où il édite.
*
* Le contrôle porte donc sur les deux choses qui ont menti ensemble :
*
* 1. **le nombre écrit en toutes lettres doit correspondre au tableau.** C'est
* « un fait recopié à la main finit toujours par mentir », dans sa forme la
* plus pure : le mot « trois » vivait loin de ce qui le fait changer, donc
* il n'avait aucune raison de changer ;
* 2. **les deux langues doivent lister exactement les mêmes dépôts.** Ajouter
* une application d'un seul côté est l'erreur suivante, et elle est
* invisible à la relecture d'une seule moitié.
*
* **Ce que ce contrôle ne voit pas, et il faut le savoir pour ne pas lui faire
* confiance au-delà.** Il n'aurait *pas* attrapé le défaut d'origine : le texte
* annonçait « trois » et le tableau en listait trois — faux, mais cohérent avec
* lui-même. Vérifié en remettant le fichier dans cet état exact, et il passe.
*
* La raison est structurelle : ce dépôt est seul, il ne connaît pas la liste
* des applications de la maison. La déduire des dossiers voisins marcherait ici
* et nulle part ailleurs — c'est la leçon inverse de la fusion d'Ohmnia, où
* seul un clone pour de vrai prouvait quelque chose.
*
* Ce qu'il attrape est donc la **dérive** : une application ajoutée sans que le
* nombre suive, ou ajoutée dans une seule langue. C'est la panne qui arrivera
* la prochaine fois. L'omission complète, elle, se voit sur la page d'accueil
* du site — que `Site/verifier.mjs` contrôle, marque par marque, depuis
* `logos/`.
*/
const RACINE = resolve(dirname(fileURLToPath(import.meta.url)))
const PROFIL = readFileSync(join(RACINE, 'profile/README.md'), 'utf-8').replaceAll('\r\n', '\n')
let echecs = 0
const echec = (message) => {
console.log(` ÉCHEC : ${message}`)
echecs += 1
}
/**
* Les dépôts cités dans les lignes de tableau, dans l'ordre, par langue.
*
* On repère les moitiés par leur titre, pas par une position : insérer une
* section entre les deux ne doit pas fausser le découpage.
*/
const debutAnglais = PROFIL.indexOf('## 🇬🇧')
if (debutAnglais < 0) echec('la moitié anglaise est introuvable — le contrôle ne regarderait rien')
const moities = {
français: PROFIL.slice(0, debutAnglais < 0 ? PROFIL.length : debutAnglais),
anglais: debutAnglais < 0 ? '' : PROFIL.slice(debutAnglais)
}
const depotsDe = (texte) =>
[...texte.matchAll(/github\.com\/ResonLab\/([a-z]+)\)/g)].map((t) => t[1])
const depots = {}
for (const [langue, texte] of Object.entries(moities)) {
depots[langue] = depotsDe(texte)
if (depots[langue].length === 0) {
echec(`aucun dépôt relevé dans la moitié ${langue} — le contrôle ne regarderait rien`)
}
}
console.log('\n── Les deux langues listent la même chose ──')
const listeFr = depots['français'].join(', ')
const listeEn = depots['anglais'].join(', ')
if (listeFr !== listeEn) {
echec(`les deux moitiés ne listent pas les mêmes dépôts — fr : ${listeFr} · en : ${listeEn}`)
} else {
console.log(` OK ${depots['français'].length} dépôt(s) des deux côtés : ${listeFr}`)
}
console.log('\n── Le nombre écrit correspond au tableau ──')
/**
* Nexika n'est pas une application mais le serveur, et la phrase le dit à
* part : « quatre applications de bureau […] et un serveur ». On ne compte
* donc pas son dépôt parmi les applications.
*/
const SERVEUR = 'nexika'
const NOMBRES = {
français: { 2: 'deux', 3: 'trois', 4: 'quatre', 5: 'cinq', 6: 'six', 7: 'sept' },
anglais: { 2: 'two', 3: 'three', 4: 'four', 5: 'five', 6: 'six', 7: 'seven' }
}
for (const [langue, texte] of Object.entries(moities)) {
const applications = depots[langue].filter((d) => d !== SERVEUR)
const attendu = NOMBRES[langue][applications.length]
if (!attendu) {
echec(`${applications.length} application(s) dans la moitié ${langue} : nombre non prévu`)
continue
}
// La phrase d'introduction est la première du paragraphe qui précède le
// tableau ; on cherche le mot dans toute la moitié, en tête de phrase ou non.
const present = new RegExp(`\\b${attendu}\\b`, 'i').test(texte)
const autres = Object.entries(NOMBRES[langue])
.filter(([n]) => Number(n) !== applications.length)
.filter(([, mot]) => new RegExp(`\\b${mot}\\b\\s+(applications|desktop)`, 'i').test(texte))
.map(([, mot]) => mot)
if (!present) {
echec(
`moitié ${langue} : ${applications.length} application(s) listée(s), ` +
`mais le mot « ${attendu} » n'apparaît nulle part`
)
} else if (autres.length > 0) {
echec(
`moitié ${langue} : le tableau donne ${applications.length} application(s) ` +
`mais le texte annonce encore « ${autres.join(', ')} »`
)
} else {
console.log(` OK ${langue} : « ${attendu} » et ${applications.length} application(s)`)
}
}
console.log('\n── Chaque ligne porte le logo de son dépôt ──')
let lignesVerifiees = 0
for (const ligne of PROFIL.split('\n')) {
const logo = ligne.match(/logos\/([a-z]+)\.svg/)
const lien = ligne.match(/github\.com\/ResonLab\/([a-z]+)\)/)
if (!logo || !lien) continue
lignesVerifiees += 1
if (logo[1] !== lien[1]) {
echec(`une ligne montre le logo « ${logo[1]} » et pointe vers le dépôt « ${lien[1]} »`)
}
}
// « Si tu n'examines rien, dis-le » : zéro ligne appariée n'est pas un succès,
// c'est un motif qui a cessé de reconnaître le tableau.
if (lignesVerifiees === 0) echec('aucune ligne logo↔dépôt appariée — le contrôle ne regarde rien')
else console.log(` OK ${lignesVerifiees} ligne(s), chaque logo correspond à son dépôt`)
console.log(
echecs === 0 ? '\nPROFIL COHÉRENT' : `\n${echecs} PROBLÈME(S) SUR LE PROFIL DE L'ORGANISATION`
)
process.exit(echecs === 0 ? 0 : 1)