npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@zevra/legal

v1.3.0

Published

Les documents légaux de Zevra — CGVU, mentions légales, confidentialité, DPA. Source unique, versionnée et archivée.

Readme

@zevra/legal

Les documents légaux de Zevra — CGVU, mentions légales, politique de confidentialité, DPA — en un seul exemplaire, daté et archivé.

Ce dépôt existe pour une raison simple : le même texte s'affichait dans la vitrine, dans Mémoire, dans Campus et dans le tunnel d'inscription, et rien ne garantissait que c'était le même. Il est désormais écrit une fois.

Ce que le paquet garantit — et ce qu'il ne garantit pas

Il garantit qu'un texte existe en un seul endroit, qu'il est daté, et qu'une version publiée ne change jamais (voir « L'archive est immuable »).

Il ne garantit pas, à lui seul, que toutes les applications affichent la même chose : une application épinglée à @zevra/[email protected] continuera d'afficher le texte de la 1.0.0. Le paquet est la source, pas le facteur. La propagation, c'est la section suivante, et c'est un choix par usage.

Faire qu'une mise à jour se répercute partout

Trois modes, du plus sûr au plus souple. Une application peut les mélanger.

1. Le lien — à préférer partout où c'est possible

Un pied de page, une mention au bas d'un formulaire, un e-mail : on lie vers la page canonique (document.canonique, par exemple https://zevra.tech/cgvu) au lieu de reproduire le texte.

C'est le seul mode où la dérive est structurellement impossible : il n'y a qu'un texte, à une adresse. Zéro build, zéro version, zéro entretien. Tout ce qui n'a pas une bonne raison d'embarquer le texte doit lier.

2. La lecture à l'exécution — quand le texte doit être DANS l'app

Le tunnel d'inscription, une page légale in-app : l'utilisateur ne doit pas quitter le flux. L'application lit alors le JSON servi à une adresse stable :

// Next : revalidation toutes les heures, propagation sans reconstruire
const doc = await fetch('https://zevra.tech/legal/cgvu.json', {
  next: { revalidate: 3600 },
}).then((r) => r.json())

Ces fichiers sont produits par pnpm bundle (dossier bundle/) et servis par le site. Une mise à jour se propage à la revalidation suivante, sans reconstruire aucune application — c'est ce mode qui répond à « je veux que ça se répercute partout ».

⚠️ Prévoir le cas où la lecture échoue : le repli est le texte embarqué via le paquet (mode 3), jamais une page vide.

3. La construction — quand il faut du déterminisme

import { enVigueur } from '@zevra/legal'
const cgvu = enVigueur('cgvu')

Le texte est figé au build. C'est le mode du repli et celui des usages où la reproductibilité prime (génération d'un PDF contractuel, e-mail transactionnel). Il exige une reconstruction pour se mettre à jour : c'est son défaut, et c'est aussi sa qualité.

Détecter une application en retard

bundle/index.json pèse quelques centaines d'octets et dit ce qui est en vigueur. Une application, ou un contrôle en intégration continue, peut le comparer à ce qu'elle embarque :

import { enVigueur } from '@zevra/legal'
const manifeste = await fetch('https://zevra.tech/legal/index.json').then((r) => r.json())
const distant = manifeste.documents.find((d) => d.id === 'cgvu')
if (distant.version !== enVigueur('cgvu').version) {
  // cette app sert un texte périmé — à signaler, pas à corriger en silence
}

Le rendu React — @zevra/legal/react

Le paquet principal est des données : il dit ce qu'un texte est, pas comment il se peint. Ce sous-chemin (depuis la 1.3.0) est le rendu commun, extrait de lexform, pour qu'une app n'ait plus à réécrire le sien — et qu'un correctif d'affichage se fasse une fois pour toutes les apps.

Il exporte deux composants et la mémoire du consentement :

| Export | Quoi | Où | |---|---|---| | <PageLegale id="cgvu" /> | la page d'un document, dans sa version en vigueur | composant serveur, sans état | | <BandeauCookies onDecision={…} /> | le bandeau de consentement | composant client | | lireDecision() / enregistrerDecision() | la décision mémorisée ('all' | 'no' | null) | sans React |

Prérequis : react >= 19 et la feuille de @zevra/ui chargée par l'app — les composants n'emploient que ses tokens (--ink, --paper, --accent…), aucune couleur en dur, rayon 0.

Adopter dans une app Next — quatre routes et un bandeau

// src/app/cgvu/page.tsx — même chose pour mentions-legales, confidentialite, dpa
import type { Metadata } from 'next'
import { enVigueur } from '@zevra/legal'
import { PageLegale } from '@zevra/legal/react'

export const metadata: Metadata = { title: enVigueur('cgvu').titre }

export default function Page() {
  return (
    <main className="zvl">
      <PageLegale id="cgvu" marque={{ nom: 'Campus', href: '/' }} />
    </main>
  )
}
// src/app/layout.tsx
import { BandeauCookies } from '@zevra/legal/react'

export default function RootLayout({ children }) {
  return (
    <html lang="fr">
      <body>
        {children}
        {/* Sans tracker : l'app relaie la décision à son outil de mesure,
            au chargement (décision mémorisée) comme au clic. */}
        <BandeauCookies lien="/confidentialite" />
      </body>
    </html>
  )
}

Une app qui remplace son propre bandeau garde ses clés de mémorisation, sinon chaque visiteur revoit la question une fois :

<BandeauCookies cles={{ stockage: 'lf-cookie-consent', cookie: 'lf-consent' }} onDecision={appliquer} />

Options de <PageLegale> : document={…} pour une version précise (une archive, ou le JSON lu à l'exécution — mode 2 ci-dessus), langue="en" pour une traduction (servie avec l'avertissement ; si elle manque, le français est rendu sans prétendre autre chose), le="2026-03-01" pour lire le texte applicable à une date, textes et styles={false} pour l'habillage.

L'identifiant qui compte

version — par exemple cgvu-2026-02-22 — et jamais le numéro npm.

Quand un utilisateur accepte un document, c'est cet identifiant qu'on enregistre, avec la date d'acceptation prise sur l'horloge du serveur :

import { referenceAcceptation } from '@zevra/legal'
await db.acceptations.insert({
  utilisateur: id,
  ...referenceAcceptation('cgvu'), // { document, version, applicableAu, canonique }
  accepteLe: new Date(),
})

Enregistrer « CGV acceptées : oui » ne prouve rien — il faut savoir lesquelles. Le jour où il faut le prouver :

import { version } from '@zevra/legal'
const texteAccepte = version('cgvu-2026-02-22')

L'archive est immuable

Une version publiée ne se modifie pas : des gens l'ont acceptée. Corriger une clause — même une virgule — c'est une nouvelle version datée, et l'ancienne reste.

Deux gardes le tiennent, parce qu'un mot changé dans un contrat de 3 000 mots ne se voit pas à la relecture d'un diff :

  • scripts/build-empreintes.mjs refuse d'écrire si l'empreinte SHA-256 d'une archive existante a changé, et le dit ;
  • tests/documents.test.js vérifie que chaque archive porte encore son empreinte, et qu'aucune n'échappe au registre.

Publier une mise à jour

  1. Créer documents/<id>/<date-d-entrée-en-vigueur>.json — copier la version précédente et l'amender. Ne jamais toucher l'ancien fichier.
  2. pnpm test — les gardes passent, l'ancienne archive est intacte.
  3. npm version minor --no-git-tag-version puis pousser : la CI publie.
  4. Reconstruire le site, qui sert la page canonique et le bundle/.

La date du nom de fichier est la date d'entrée en vigueur, pas celle de la rédaction : un texte daté du futur n'est pas encore en vigueur (enVigueur l'ignore), ce qui permet d'annoncer une mise à jour avant qu'elle ne s'applique — comme l'article « Modifications » de la CGVU le prévoit.

Reprise initiale

Les quatre documents ont été importés depuis les pages Astro de zevra-website par scripts/importer-depuis-site.py, verbatim : le script ne corrige ni l'orthographe, ni la ponctuation, et il échoue si le texte produit ne contient pas exactement les mots de la page — ni un de moins, ni un de plus.

À partir de maintenant, ce dépôt est la source : le site devrait lire le paquet plutôt que porter le texte en dur. Tant que ce n'est pas fait, les deux existent en parallèle et peuvent diverger — c'est la première chose à régler.

Les accents (rectification du 27/08/2026)

Les quatre documents avaient été publiés sans accents (« Conditions generales », « donnees personnelles »). C'est corrigé : les versions *-2026-08-27 portent nature: 'rectification' et rectifieDe, et les versions du 22/02 restent servies telles quelles — quelqu'un les a acceptées sans accents, c'est ce texte-là qui l'engage.

nature: 'rectification' dit qu'aucune clause n'a bougé : une application peut s'en servir pour ne pas redemander l'acceptation. Redemander à chaque virgule use le consentement et finit par le vider de son sens.

La restitution est faite par scripts/restituer-accents.py, qui échoue si, en retirant les accents du texte produit, on ne retrouve pas l'ancien au caractère près : aucun mot ne peut être ajouté, retiré ni altéré en chemin. Les formes dont l'accent dépend du contexte ont été relevées une par une — les 154 « a » (préposition partout sauf trois : a commencé, a reconnu, a recours), les quatre « dès », et « encadre / autorise / informe », tantôt verbe tantôt participe.

Ce que la rectification n'a PAS touché

Quatre défauts du texte source subsistent : ce ne sont pas des accents, et les corriger changerait des mots — donc le fond, donc une décision d'édition, pas une rectification typographique.

| Où | Écrit | Devrait être | |---|---|---| | CGVU, art. 11 | definitivamente (espagnol) | définitivement | | DPA, ×3 | liceiete | licéité | | CGVU, en-tête | une SAS immatricule | immatriculée (accord) | | 5 occurrences | met en oeuvre | met en œuvre (ligature) |

À trancher : les trois premiers sont des fautes, le quatrième une finesse typographique. Une fois décidés, ils donneront une nouvelle version — de nature: 'modification' pour les fautes de mot, puisque le texte change.