From ff5c9e1806e9197c1f6b1229ac9284ff136ac022 Mon Sep 17 00:00:00 2001 From: "alban.pasquelin" Date: Tue, 8 Sep 2026 18:38:57 +0200 Subject: [PATCH 1/3] =?UTF-8?q?Rend=20au=20contr=C3=B4le=20des=20licences?= =?UTF-8?q?=20les=20paquets=20qu'il=20concluait=20sans=20ouvrir?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `licenceDe` balayait `node_modules/.pnpm` à `-maxdepth 4`. Un nom plat y tient en quatre segments, un nom scopé en cinq : 8 des 25 paquets déclarés ici rendaient `null`, et `if (licence === null) continue` les comptait comme jugés. Le script concluait « 25 paquets, tous permissifs » en en ayant ouvert 17. La lecture passe par le lien que le gestionnaire pose pour toute dépendance déclarée — alias `npm:` compris, scopes compris. Et le silence devient un échec : un paquet que ce dépôt installe et qu'on ne peut pas ouvrir rougit la porte en se nommant. Les pairs gardent la tolérance, qui avait été écrite pour elles — ce dépôt déclare `dockview-react` en pair optionnelle, que le gestionnaire n'installe pas de lui-même. Fichier du socle partagé : identique octet pour octet dans map3D et AI Desktop Studio, où la correction est portée telle quelle. --- scripts/check-licences.mjs | 46 +++++++++++++++++++------------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/scripts/check-licences.mjs b/scripts/check-licences.mjs index 00e89c7..d5b2665 100644 --- a/scripts/check-licences.mjs +++ b/scripts/check-licences.mjs @@ -6,11 +6,10 @@ * Écrit parce que ce fichier est une affirmation juridique, et qu'une affirmation que * personne ne revérifie dérive à la première montée de version. * - * CE FICHIER EST PARTAGÉ À L'IDENTIQUE par map3D, panels et IA Studio. La liste blanche, + * CE FICHIER EST PARTAGÉ À L'IDENTIQUE par map3D, panels et AI Desktop Studio. La liste blanche, * les paquets embarqués et la nature du dépôt vivent dans `repo.config.json`. */ import { readFileSync, existsSync } from 'node:fs' -import { execFileSync } from 'node:child_process' import { resolve } from 'node:path' const root = resolve(import.meta.dirname, '..') @@ -54,37 +53,35 @@ for (const nom of EMBARQUES) { if (!propre.devDependencies?.[nom]) problemes.push(`${nom} est déclaré embarqué mais n'est pas installé.`) } -/** La licence qu'un paquet déclare, lue dans l'arbre installé. */ +/** + * La licence qu'un paquet déclare, par le lien que le gestionnaire pose pour toute dépendance + * déclarée — alias `npm:` compris. Le balayage de `node_modules/.pnpm` qui la précédait s'arrêtait + * un segment trop haut, et ne voyait aucun paquet scopé. + */ function licenceDe(nom) { try { - const trouves = execFileSync('find', [ - 'node_modules/.pnpm', - '-maxdepth', - '4', - '-path', - `*/node_modules/${nom}/package.json`, - ]) - .toString() - .split('\n') - .filter(Boolean) - - if (trouves.length === 0) return null - return JSON.parse(readFileSync(trouves[0], 'utf8')).license ?? null + return JSON.parse(readFileSync(resolve(root, 'node_modules', nom, 'package.json'), 'utf8')) + .license ?? null } catch { return null } } -const declarees = [ +const installees = [ ...Object.keys(propre.dependencies ?? {}), ...Object.keys(propre.devDependencies ?? {}), - ...Object.keys(propre.peerDependencies ?? {}), ] +// Une pair est déclarée pour le consommateur, et le gestionnaire n'installe pas celles qui sont +// optionnelles : absente ici, elle n'a rien à juger. Ce que CE dépôt installe, si. +const declarees = [...installees, ...Object.keys(propre.peerDependencies ?? {})] +const lues = declarees.map(nom => [nom, licenceDe(nom)]) -for (const nom of declarees) { - const licence = licenceDe(nom) - if (licence === null) continue // pas installé ici ; rien à juger - if (!AUTORISEES.has(licence)) problemes.push(`${nom} est en ${licence}, absent de la liste blanche.`) +for (const [nom, licence] of lues) { + // Taire l'échec de lecture est ce qui faisait passer pour jugé tout paquet que la lecture ratait. + if (licence === null && installees.includes(nom)) + problemes.push(`${nom} est déclaré mais sa licence est illisible : paquet absent, ou sans champ « license ».`) + else if (licence !== null && !AUTORISEES.has(licence)) + problemes.push(`${nom} est en ${licence}, absent de la liste blanche.`) } if (problemes.length > 0) { @@ -93,4 +90,7 @@ if (problemes.length > 0) { process.exit(1) } -console.log(`Licences contrôlées — ${declarees.length} paquets, tous permissifs, mentions présentes.`) +const jugees = lues.filter(([, licence]) => licence !== null).length +console.log( + `Licences contrôlées — ${jugees} paquets lus sur ${declarees.length} déclarés, tous permissifs, mentions présentes.`, +) From a13755949c933526ab73f9adfa4332c96d19ad7a Mon Sep 17 00:00:00 2001 From: "alban.pasquelin" Date: Wed, 23 Sep 2026 22:38:30 +0200 Subject: [PATCH 2/3] =?UTF-8?q?Mesure=20la=20fr=C3=A9quentation=20de=20pas?= =?UTF-8?q?quelin.github.io/panels,=20avec=20consentement?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Une ligne dans le gabarit, donc dans les quinze langues à la fois : le bandeau de choix et le chargement de Google Analytics vivent dans un seul fichier partagé, servi par www.pasquelin.com et commun aux quatre sites. Une correction s'y fait une fois et non quatre ; son code et ses tests sont dans le dépôt `pasquelin/site`, sous `public/shared/`. Rien n'est demandé à Google avant un accord explicite du visiteur, et le panneau emprunte les couleurs, la police et les arrondis de cette page plutôt que d'imposer les siens. Propriété GA4 : G-SZ5H3MS3D4 — un identifiant de mesure n'est pas un secret, toute page qui le porte le montre à qui lit sa source. --- site/template.html | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/site/template.html b/site/template.html index 2752e4f..6942246 100644 --- a/site/template.html +++ b/site/template.html @@ -42,6 +42,14 @@ + + +