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

nohu-cms

v0.1.1

Published

Admin de contenu pour sites Astro statiques sur Cloudflare : textes, images et articles dans D1 et R2, connexion par Cloudflare Access.

Readme

nohu-cms

npm CI

L'éditeur d'une page dans l'admin

Admin de contenu pour sites Astro statiques hébergés sur Cloudflare : textes, photos et articles modifiables par un client non technique, sans serveur à maintenir.

  • Le site reste 100 % statique. Chaque enregistrement dans l'admin relance le build (Workers Builds), qui relit la base et republie le site en 2 à 3 minutes.
  • Données dans D1, photos dans R2 (redimensionnées dans le navigateur, servies avec srcset), connexion par Cloudflare Access (code reçu par email).
  • L'admin se génère depuis un seul fichier, cms.config.ts : pages → sections → champs, et collections (articles, événements…).
  • Chaque site a sa base, son stockage et son application Access : un client ne voit jamais un autre site.
Admin (/admin) ──► Worker du site : /api/admin ──► D1 (textes, articles)
                                │              └► R2 (photos, /media/*)
                                └► Deploy hook ──► Workers Builds
                                                   astro build (lit D1) ──► wrangler deploy

Installation

Dans un site Astro existant (rendu statique) :

npm i nohu-cms
npm i -D wrangler @cloudflare/workers-types
npx wrangler login
npx nohu-cms init

init pose quelques questions (nom, domaines, emails des admins), crée la base D1 et le stockage R2, écrit les fichiers du site (ou complète un wrangler.jsonc existant, en gardant une copie de l'original), puis :

  1. connecte le dépôt Git au Worker si ce n'est pas fait (Workers Builds : build, puis npx wrangler deploy) ;

  2. termine la mise en service avec un seul jeton API :

    CLOUDFLARE_API_TOKEN=<jeton> npx nohu-cms setup [--production-branch main]

    setup (relançable sans risque : il ne refait que ce qui manque) crée ou vérifie la base D1 et ses tables, le stockage R2, l'application Cloudflare Access et sa règle (= ADMIN_EMAILS), les jetons du build (CMS_API_TOKEN, D1 Write) et du suivi des mises en ligne (CMS_BUILDS_TOKEN, Workers CI Read), et signale la branche de production (il ne la change qu'avec --production-branch, puisque c'est ce qui met le site en ligne). Sans jeton, la commande liste les droits à lui donner. Aucun secret n'est affiché ni écrit sur disque ;

  3. crée le deploy hook dans le tableau de bord (Cloudflare n'a pas d'API pour ça), puis npx nohu-cms set-hook <url> ;

  4. npx nohu-cms doctor pour tout vérifier.

Avec Claude Code, npx nohu-cms skill installe le skill nohu-cms-setup, qui fait l'installation et transforme les textes en dur de tes pages en contenus modifiables.

Fichiers du site

cms.config.ts      contenus modifiables (defineCms)
astro.config.mjs   integrations: [nohuCms()]  → page /admin
worker/index.ts    export default createCmsWorker(config)
wrangler.jsonc     Worker, D1, R2, Access, ADMIN_EMAILS (généré par init)
.dev.vars          DEV_ADMIN_EMAIL pour l'admin locale (jamais commité)

Déclarer le contenu

// cms.config.ts
import {
  collection,
  defineCms,
  image,
  list,
  page,
  richtext,
  section,
  text,
  textarea,
} from 'nohu-cms';

export default defineCms({
  admin: { title: 'Atelier Dupont', brandColor: '#2f5d50' },
  pages: {
    accueil: page('Accueil', '/', {
      hero: section('Haut de page', {
        title: text('Titre', 'Bienvenue', { max: 70 }),
        photo: image('Photo', { src: '/images/hero.jpg', alt: 'L’atelier' }),
      }),
      tarifs: section('Tarifs', {
        items: list(
          'Tarifs',
          {
            itemLabel: 'un tarif',
            itemTitle: 'name',
            fields: { name: text('Nom'), price: text('Prix') },
          },
          [{ name: 'Séance', price: '20 €' }],
        ),
      }),
    }),
  },
  collections: {
    articles: collection('Articles', {
      itemLabel: 'un article',
      path: '/blog/[slug]',
      fields: {
        summary: textarea('Résumé', '', { max: 220 }),
        cover: image('Photo de couverture'),
        body: richtext('Texte'),
      },
    }),
  },
});

| Champ | Valeur | Notes | | ---------------------- | ---------------------------------------- | ----------------------------------------------------------------- | | text, textarea | string | max, optional, help | | url, email, date | string | validés ; date AAAA-MM-JJ | | richtext | HTML | titres, listes, liens, photos ; nettoyé au build | | image | { src, alt, width?, height?, srcset? } | asset(…) ou null en valeur d'origine (voir Images) | | select | string | select('Lieu', ['sol', 'airs']) ou { value, label } | | list | tableau d'objets | sous-champs simples, select, richtext, image ; min, max |

Option commune hidden: true : champ technique absent de l'admin (ancre d'un lien de menu, cadrage d'une photo…). Dans une liste, sa valeur suit son élément quand la cliente réordonne ; un nouvel élément reçoit la valeur d'origine du champ.

Les valeurs écrites dans la config sont les valeurs d'origine : la base ne stocke que ce qui en diffère. Changer une valeur d'origine met donc le site à jour, sauf si le champ a été modifié dans l'admin.

Chaque entrée de collection a d'office title, slug, status (draft ou published) et publishedAt ; ajouter un champ à une collection ne demande aucune migration.

Renommer ou supprimer un champ change sa clé de stockage (page.section.champ) : une valeur modifiée dans l'admin est alors ignorée, et le build l'écrit dans ses logs.

Images

Les photos du site restent dans src/ et sont optimisées au build par astro:assets, même quand elles sont modifiables :

import { asset, image } from 'nohu-cms';

photo: image('Photo', asset('assets/photos/grange.jpg', 'La grange en pierres')),
  • asset(chemin depuis src/, description) : image du projet. <CmsImage> la passe à <Image> d'Astro (formats modernes, plusieurs largeurs, widths et sizes réglables). L'admin en affiche une vignette générée au build.
  • Une photo envoyée dans l'admin la remplace : elle est redimensionnée dans le navigateur (WebP, 3 largeurs), stockée dans R2 et servie sur /media/…. « Rétablir la photo d'origine » revient à l'image du projet.
  • Seules les images du projet citées dans la config peuvent être enregistrées (contrôle côté serveur) : une liste réordonnée garde les siennes.
  • Un chemin commençant par / désigne un fichier de public/, affiché tel quel.

Afficher le contenu

---
import config from '../../cms.config';
import { getContent, getEntries, getEntryPath } from 'nohu-cms/content';
import CmsImage from 'nohu-cms/components/CmsImage.astro';
import Lines from 'nohu-cms/components/Lines.astro';
import RichText from 'nohu-cms/components/RichText.astro';

const { accueil } = await getContent(config);
const articles = await getEntries(config, 'articles');
---

<h1>{accueil.hero.title}</h1>
<CmsImage image={accueil.hero.photo} sizes="100vw" loading="eager" />
{articles.map((a) => (
  <a href={getEntryPath(config, 'articles', a)}>{a.title}</a>
))}
  • getContent(config) : tous les contenus, typés depuis la config.
  • getEntries(config, nom) : entrées publiées, triées (sort de la collection : date-desc par défaut, date-asc, title).
  • getEntry(config, nom, slug), getEntryPath(config, nom, entrée).
  • Composants : CmsImage (srcset), Lines (retours à la ligne, linkify pour emails et téléphones), RichText (HTML déjà nettoyé).
  • Utilitaires (nohu-cms) : toParagraphs, formatDate, phoneHref.

Le HTML des champs richtext passe par une liste blanche au build : seules les mises en forme prévues et les photos envoyées dans l'admin (/media/…) passent.

Source du contenu au build

| CMS_SOURCE | Source | | ------------ | --------------------------------------------------------------------------------------------------------------------- | | d1 | base de production (API REST, CMS_API_TOKEN) — choisi automatiquement si le compte, la base et le jeton sont connus | | local | base locale de wrangler dev (nohu-cms dev) | | defaults | valeurs d'origine, aucune entrée (choisi sinon) |

Sur Workers Builds, un build sans accès à D1 échoue volontairement : on ne republie jamais les valeurs d'origine par-dessus les modifications.

Admin locale

npm run dev:admin   # = nohu-cms dev

http://localhost:8788/admin : D1 et R2 locales, connexion simulée avec DEV_ADMIN_EMAIL (.dev.vars, accepté uniquement sur localhost), et reconstruction du site à chaque enregistrement, comme en production.

Suivi des mises en ligne

Après chaque enregistrement, le deploy hook renvoie l'identifiant du build lancé. Avec le secret CMS_BUILDS_TOKEN (jeton de compte « Workers CI Read », créé par npx nohu-cms setup), l'admin affiche l'état réel de la mise en ligne : en cours, en ligne depuis telle heure, ou échec (le site affiche alors encore la version précédente, et « Republier » est proposé). Sans ce jeton, l'admin estime l'état à partir de l'heure de la demande.

Le cron */10 * * * * du Worker vérifie aussi le dernier build : s'il a échoué, une alerte est envoyée une seule fois au webhook ALERT_WEBHOOK_URL (secret facultatif ; corps JSON avec text et content, compris par Slack, Discord ou ntfy). Le nettoyage des photos reste lié au cron quotidien 0 3 * * *.

Sécurité

  • /admin et /api/admin sont protégés par l'application Cloudflare Access du site. Le Worker vérifie en plus la signature du jeton Access, son audience (ACCESS_AUD, propre au site) et l'email (ADMIN_EMAILS, seule liste des comptes autorisés).
  • Les écritures envoyées depuis un autre site sont refusées (contrôle d'origine).
  • Ajouter ou retirer un admin = modifier ADMIN_EMAILS et la règle de l'application Access.
  • Les photos inutilisées depuis plus de 24 h sont supprimées chaque nuit (cron du Worker).

Routes propres au site

export default createCmsWorker(config, {
  fetch: (request, env) =>
    new URL(request.url).pathname === '/api/contact'
      ? handleContact(request, env)
      : null,
});

Seuls les chemins de assets.run_worker_first atteignent le Worker.

CLI

| Commande | Rôle | | -------------------------------------- | ------------------------------------------------------ | | nohu-cms init [--dry-run] | installe nohu-cms dans le projet courant | | nohu-cms doctor | vérifie fichiers, base, secrets, protection de l'admin | | nohu-cms dev [--port 8788] | admin locale | | nohu-cms migrate [--local\|--remote] | applique les migrations | | nohu-cms set-hook <url> | secret DEPLOY_HOOK_URL du Worker | | nohu-cms skill | copie le skill Claude dans .claude/skills/ |

Mises à jour

npm update nohu-cms. Les nouvelles migrations de la base sont appliquées automatiquement au build suivant (ou npx nohu-cms migrate --remote).

Licence

MIT