fraza-module
Construire, vérifier et publier un paquet Fraza, le core comme un module, avec les mêmes outils.
fraza-module construit un paquet de l'écosystème, vérifie qu'il peut être publié et le publie. Il s'adresse à qui développe le core, un module officiel ou un module tiers, jamais à un site : la commande se lance depuis la racine du dépôt du paquet.
npx fraza-module <commande>Sans argument, la commande affiche son aide.
Chaque dépôt portait sa propre copie de ces scripts, retouchée à la main là où les paquets diffèrent. Les copies ont dérivé. Une seule implémentation vit maintenant dans @fraza/core, sous src/tools/, et ce qui change d'un paquet à l'autre se déduit du paquet lui-même : son nom, ses exports, l'emplacement de ses icônes. Un module tiers obtient les mêmes outils en installant le core, qu'il déclare déjà en peerDependencies.
Le core lance ces commandes depuis ses sources (node ./src/tools/cli.js), puisque son dist/ n'existe pas encore au moment de son propre build. Un module les lance depuis le core installé, sous node_modules/@fraza/core/dist/tools/. @fraza/start, qui n'installe pas le core, les lance par son scripts/fraza-module.js, sur le core buildé du dépôt voisin ../core, à défaut de node_modules/@fraza/core. Rien sous dist/tools/ n'est importé par un site à l'exécution.
Commandes
| Commande | Effet |
|---|---|
build | Régénère le module de version, compile avec tsup, régénère THIRD-PARTY-NOTICES.md, puis lance check-colors et check-bundle. |
sync-version | Régénère le module de version depuis package.json#version. Avec --check, n'écrit rien et sort en 1 si le fichier est périmé. |
notices | Régénère THIRD-PARTY-NOTICES.md depuis l'arbre de production installé. Sort en 1 si une licence copyleft y apparaît. |
changelog | Régénère CHANGELOG.md, changelog.json et le résumé package.json#fraza depuis l'historique git. |
check | Valide un module construit contre le contrat de module, et liste les sous-chemins de @fraza/core qu'il importe. Sans effet sur le core et le starter. |
check-colors [dossier…] | Cherche les nuances de palette dans le code compilé, dist par défaut. |
check-bundle | Vérifie que les pages publiques n'atteignent pas les barrels lourds du core, que les sous-entrées client du core n'atteignent pas de code serveur, et que sideEffects reste sûr. |
release [patch|minor|major|X.Y.Z] | Vérifie le paquet, monte sa version, committe, publie, puis pose l'étiquette et la pousse. patch par défaut, un numéro exact pour aligner plusieurs paquets, --dry-run pour simuler. |
release-check | Vérifie que le paquet peut être publié tel quel. Ne modifie et ne publie rien. |
publish | Publie la version actuelle sur chaque registre où elle manque : npmjs puis registry.fraza.ca pour @fraza/*, registry.fraza.ca pour @fraza-pro/*. --dry-run pour simuler. |
test-clean | Supprime les schémas jetables que les tests d'intégration ont laissés sur la base de FRAZA_TEST_DATABASE_URL. |
help | Affiche l'aide. |
Toutes les commandes sortent en 0 en cas de succès et en 1 sur un échec, détaillé sur la sortie d'erreur.
build
npx fraza-module buildLes étapes s'enchaînent dans cet ordre, et la première qui échoue arrête la commande :
sync-version, avant la compilation pour que le littéral compilé soit le bon ;tsup, celui installé dans le paquet, qui lit sontsup.config.js;notices;check-colors dist;check-bundle.
Les deux contrôles lisent dist/, où esbuild a déjà retiré les commentaires : une classe citée dans un commentaire ne produit donc pas de faux positif. tsup reste une dépendance de développement de chaque paquet, à la version qu'il choisit : le core ne le livre pas.
Le core ajoute ses propres étapes après celles-ci, dans son script build : le contrôle du catalogue de variables d'environnement, la copie de la feuille de style et des polices, la copie de docs/.
sync-version
npx fraza-module sync-version
npx fraza-module sync-version --checkLe module de version porte un littéral, export const MODULE_VERSION = '0.3.1', régénéré depuis package.json#version. Un littéral compilé résiste au traçage de output: 'standalone' et à la réécriture de import.meta.url par Turbopack, deux cas où une lecture de package.json à l'exécution casse. Le fichier est committé.
| Paquet | Fichier | Constante |
|---|---|---|
@fraza/core | src/shared/lib/version.js | CORE_VERSION |
@fraza/start | bin/version.js | STARTER_VERSION |
| Tout autre paquet | src/version.js | MODULE_VERSION |
--check sert en prepublishOnly : il empêche de publier un module de version périmé.
notices
npx fraza-module noticesLa commande lit npm ls --omit=dev --omit=peer --all, donc l'arbre réellement installé, et écrit l'inventaire des licences. Elle retrouve le dossier de chaque paquet comme Node le résout, dans le node_modules/ le plus proche du paquet qui l'utilise, et pas dans le chemin qu'affiche npm : npm masque par *** chaque segment en forme d'UUID, si bien qu'un clone placé sous un tel dossier, comme en intégration continue ou dans un dossier temporaire, aurait listé chaque licence en UNKNOWN. Une licence GPL, AGPL, SSPL, EUPL, CDDL, EPL, MPL, OSL ou CPAL la fait échouer. Voir Licence et propriété.
Les polices recopiées dans les sources sont distribuées avec le paquet : leur licence OFL figure sous « Code tiers intégré ». La commande les repère à leurs fichiers .woff2, .woff, .ttf ou .otf, sous src/shared/fonts/ pour le core et src/fonts/ pour un module, et reproduit le OFL.txt copié à côté d'eux : la mention de droit d'auteur et le nom réservé restent ceux de l'auteur. Un dossier de polices sans OFL.txt fait échouer la commande, parce que la licence doit accompagner chaque copie. Un paquet sans police recopiée n'a pas cette section. Les icônes sont dessinées par Fraza et n'y figurent pas. Voir Polices.
changelog
npx fraza-module changelogLa commande régénère tout l'historique à chaque exécution et ne fait jamais échouer une publication : un commit hors convention est classé dans « Autres », un dépôt sans historique git est laissé en l'état. Le détail des bornes de version, des déductions et des pieds de commit est dans Notes de version.
Le résumé de la dernière version est fusionné dans package.json#fraza, pas substitué : une clé que le paquet déclare lui-même dans ce champ survit à la régénération.
check
npx fraza-module checkValide le paquet contre le contrat de module que le core installé comprend : ce qu'un module doit respecter pour s'installer et démarrer dans un projet. Chaque règle rapporte ses écarts, et la commande les affiche tous en une fois.
La plupart des règles lisent dist/, ce qui sera publié : la commande se lance après le build. Sans dist/, elle le dit et échoue. Les modules officiels la lancent en prepublishOnly, après npm run build.
| Règle | Écart signalé |
|---|---|
package.json | Pas de name ou de version, type différent de module, pas d'entrée "." dans exports, files sans dist, peerDependencies sans @fraza/core. La plage n'est pas vérifiée : les modules déclarent "*", voir Dépendances Fraza. |
| Contrat | fraza.contract absent, ou contrat que le core installé ne comprend pas. |
| Module de version | src/version.js absent. |
| Point d'entrée | src/index.js absent, ou sans export manifest. |
| Exports compilés | Une entrée de exports vise un fichier absent de dist/. |
./client | Le fichier compilé ne commence pas par 'use client'. |
./public-page | Le fichier compilé commence par 'use client'. |
./account | Le fichier compilé commence par 'use client'. |
| Graphe d'imports | Les constats de check-bundle : ./client qui atteint du code serveur, ./public, ./public-page ou ./account qui atteint un barrel lourd, sideEffects incomplet. |
| Manifeste | L'entrée "." ne s'importe pas sous Node, ou manifest s'écarte du contrat : champ inconnu, name ou version qui diffère de package.json, module requis absent de peerDependencies, optionalModules, permissions ou envVars mal formés. Pas de register. |
| Sous-chemins du core | Un @fraza/core/… importé par dist/ que package.json#exports du core installé n'expose pas. |
La sortie liste ensuite les sous-chemins de @fraza/core importés par dist/, imports dynamiques compris, qu'il y ait des écarts ou non :
✓ Contrat 1 : @fraza/module-blog est conforme.
Sous-chemins de @fraza/core importés (3), confrontés au core installé 0.2.0 :
@fraza/core/api
@fraza/core/database
@fraza/core/features/adminLa commande ne connaît pas l'historique du core : elle confronte seulement ces sous-chemins au core installé. Le module doit fonctionner avec la dernière version du core, celle que fraza-update installe.
Sur le core et sur le starter, qui ne sont pas des modules, la commande le dit et sort en 0.
check-colors
npx fraza-module check-colorsLève sur une classe de palette Tailwind, sur une couleur préfixée dark: et sur une couleur arbitraire [#…]. Un composant écrit les jetons du thème, sinon il échappe au theme.js du projet. Voir Design system.
Les exceptions justifiées, comme une palette catégorielle qui distingue des types de fichiers, sont déclarées dans src/tools/check-colors.server.js du core, par paquet et par fichier, avec leur raison. Elles vivent à un seul endroit pour tout l'écosystème : une exception reste une décision relue avec le core.
check-bundle
npx fraza-module check-bundleLe contrôle parcourt le graphe d'imports de dist/ depuis les points d'entrée publics, en suivant les imports @fraza/core/* jusque dans le dist/ du core installé. Il signale toute chaîne qui atteint le jeu d'icônes, le barrel shared/components ou l'éditeur de blocs, et l'affiche.
| Paquet | Points d'entrée publics | Atteindre un barrel lourd |
|---|---|---|
@fraza/core | features/provider, themes, toast, features/auth/client, features/account/server, shared/public, shared/public/error, shared/global-error, public-pages/server, et chaque shared/components/<Nom> sauf BlockEditor | Erreur. |
| Module | ./public, ./public-page et ./account, quand le paquet les exporte | Erreur. |
Un module a toujours un chemin léger : chaque composant s'importe par @fraza/core/shared/components/<Nom>, chaque icône par @fraza/icons/<Nom>. Le constat affiche la chaîne d'imports, du point d'entrée jusqu'au barrel, pour trouver la ligne à changer.
Un deuxième contrôle part, pour le core, de chaque sous-entrée sûre depuis un fichier 'use client', et pour un module, de son entrée ./client, que l'administration charge dans le navigateur. Il suit les imports du module jusque dans le dist/ du core. Il échoue dès que l'une d'elles atteint un fichier .server.js, un module intégré de Node ou un paquet serveur comme pg, sharp ou next/headers, et affiche la chaîne la plus courte qui y mène.
Le dernier contrôle porte sur package.json#sideEffects. Le core doit le déclarer, et la liste doit couvrir chaque fichier qui agit à l'import, c'est-à-dire qui appelle quelque chose au niveau racine, comme registerPage(…), y compris dans l'initialiseur d'une variable, un export default ou le corps d'un if, ou qui écrit sur globalThis hors de la garde d'un registre, ainsi que chaque fichier visé par un import nu. Un appel qui ne fait que calculer une valeur se marque /* @__PURE__ */. Le constat donne la ligne qui fait du fichier un fichier à effet. Les commandes de package.json#bin sont écartées. Voir Effets de bord et barrels. Un module qui ne le déclare pas est sauté, ce qui est sûr : un bundler considère alors chaque fichier comme ayant des effets, et n'en supprime aucun. Un module qui le déclare, false compris, passe le même contrôle que le core.
release
npx fraza-module release
npx fraza-module release minor
npx fraza-module release 0.2.1
npx fraza-module release --dry-run
npx fraza-module release -gAu lieu d'un niveau, un numéro exact X.Y.Z fixe la version, par exemple pour aligner le core, start et les modules sur un même numéro. Il doit être une version stable, supérieure à la version actuelle.
Publie une nouvelle version du paquet en une commande. Le seul choix qui reste au développeur est le niveau : patch par défaut, minor ou major quand il le décide. La commande calcule le numéro selon semver : depuis 0.1.14, patch donne 0.1.15, minor donne 0.2.0 et major donne 1.0.0. Voir Versionner. Les changements cassants ne décident pas du niveau : changelog les note dans les notes de version, et c'est tout. Une préversion comme 1.0.0-beta.1 est refusée : la fixer à la main.
Chaque paquet la lance par son script release, en un seul appel, pour que les arguments de npm run release -- minor arrivent à la commande.
Sans -g, la version part sur registry.fraza.ca seulement : c'est le mode des versions qui servent à tester un changement dans un projet. Avec -g, un paquet @fraza/* part aussi sur npmjs, d'où les sites installent. Un module @fraza-pro/* ne va jamais sur npmjs : -g ne change rien pour lui, et la commande le dit.
Les étapes s'enchaînent dans cet ordre, et la première qui échoue arrête la commande :
- Vérifications, avant toute écriture. Le paquet est dans un dépôt git, sur une branche qui suit une branche distante, affichée en tête. La commande lance
git fetchsur le dépôt distant de cette branche et refuse une branche en retard sur lui : sans cela, la publication partirait et seulgit push, à la fin, serait refusé. Sans réseau, elle s'arrête là. Aucune publication précédente n'est restée arrêtée après son commit de version. L'arbre de travail est propre, l'étiquettev<nouvelle version>n'existe ni en local ni sur le dépôt distant,CHANGELOG.mdetchangelog.jsonsont déclarés dansfiles, etpublishConfigest conforme (voirpublish). Puis la commande installe la dernière version publiée de chaque dépendance Fraza du paquet (npm install --no-save @fraza/…@latest, sans toucher àpackage.jsonni au lockfile). Le registre suit la publication, jamais~/.npmrc:registry.fraza.capar défaut, qui porte les dernières versions de test, npmjs avec-g, pour vérifier le paquet contre les versions qu'un site recevra avec lui. Elle lance ensuitenpm run build,checkpour un module,npm testquand le paquet déclare ce script, et l'arbre doit être resté propre après le build. Un échec ici laisse le dépôt exactement dans son état d'avant. - Montée de version. La commande affiche
ancienne → nouvelle, écrit la version danspackage.jsonet dans les entrées racine depackage-lock.json, régénère le module de version (sync-version) et les notes de version (changelog), relancenpm run build, puis committe ces fichiers seuls avec le messagechore: bump version to X.Y.Z.THIRD-PARTY-NOTICES.md, que le build régénère d'après les paquets installés, part dans ce même commit : il change quand une dépendance Fraza vient d'avancer. Un échec avant le commit remet ces fichiers dans leur état d'avant. - Publication sur chaque registre, par
publish:prepublishOnlyrelance le build et les contrôles du paquet, puis le même tarball part versregistry.fraza.ca, précédé de npmjs avec-gpour un paquet@fraza/*. - Étiquette et envoi, seulement si chaque registre a accepté la version.
git tag -a v<version> -m v<version>, puisgit push --atomic --follow-tags, qui envoie la branche et l'étiquette ensemble ou aucune des deux.
Le lockfile est modifié comme un fichier JSON, pas par npm install : un module se construit souvent contre un core installé avec --no-save, qu'une installation inscrirait dans son lockfile.
Un échec après le commit ne s'annule pas en silence. La commande s'arrête et dit où en est la publication : commit de version fait en local, aucune étiquette, rien de poussé quand un registre a refusé la version, et la liste des registres qui l'ont et de ceux où elle manque. Elle affiche les commandes pour reprendre (npm run release:publish, avec -g si la publication visait npmjs, qui ne publie que là où la version manque, puis l'étiquette et l'envoi), et git reset --hard HEAD~1 pour annuler la montée de version tant qu'aucun registre ne l'a reçue. Quand seul l'envoi échoue, elle dit si l'étiquette a atteint le dépôt distant, et propose git pull --no-rebase avant de pousser à nouveau : un rebase réécrirait le commit de version, que l'étiquette publiée désigne. Relancer npm run release à ce stade monterait la version une deuxième fois : la commande refuse de partir quand HEAD est le commit de version de la version courante, pas encore sur le dépôt distant, et redonne les commandes pour reprendre ou annuler.
| Option | Effet |
|---|---|
patch, minor, major | Niveau de la montée de version. patch par défaut. |
X.Y.Z | Version exacte, à la place d'un niveau. |
--dry-run | Fait les vérifications, build et tests compris, calcule la nouvelle version, puis affiche chaque étape qui aurait suivi. N'écrit rien dans le dépôt : aucun commit, aucune étiquette, aucun envoi, aucune publication. Seul le git fetch des vérifications met à jour les branches distantes suivies. |
-g, --global | Publie aussi sur npmjs, en premier, un paquet @fraza/*. Les dépendances Fraza des vérifications viennent alors de npmjs. |
npm run release --dry-run, sans --, donne l'option à npm et non au script. La commande le détecte et simule aussi : sans cela, npm publish ne ferait que simuler alors que l'étiquette et l'envoi auraient lieu.
npm run release -g donne de même -g à npm, qui le transmet au script comme npm_config_global. La commande le lit comme une demande de publication sur npmjs, puis le retire de son environnement : chaque npm qu'elle lance en hériterait, et npm install --no-save installerait en global.
publish
npx fraza-module publish
npx fraza-module publish --dry-run
npx fraza-module publish -gPublie la version actuelle de package.json, sans la monter, sur chaque registre du paquet où elle manque. release l'appelle à son étape 3. Chaque paquet Fraza l'expose aussi par son script release:publish, pour terminer une publication interrompue. Le core et @fraza/start n'installent pas fraza-module comme dépendance : npm run release:publish y lance le CLI du dépôt.
| Scope | Registres, dans l'ordre |
|---|---|
@fraza/* | https://registry.fraza.ca/. Avec -g : npmjs, avec --access=public, puis https://registry.fraza.ca/. |
@fraza-pro/* | https://registry.fraza.ca/ seulement, -g ou non. |
| Autre | npm publish dans le dossier, vers le registre de sa propre configuration. |
Avec -g, npmjs passe en premier : c'est le registre d'où les sites installent, et le plus strict, puisqu'un numéro de version n'y est jamais réutilisable. Un refus de npmjs arrête la publication avant que le secours ne reçoive une version que npmjs n'a pas.
Les étapes :
- Contrôle de
publishConfig. Un paquet@fraza/*déclare"access": "public"et aucunregistry: npmjs refuse un paquet de scope enrestrictedsans abonnement, et unnpm publishlancé à la main part ainsi vers npmjs, en public. Un module@fraza-pro/*déclare"registry": "https://registry.fraza.ca/": sans cette ligne, unnpm publishlancé à la main enverrait un module payant sur npmjs.releaseetrelease-checkfont le même contrôle. - Ce que chaque registre a déjà.
npm view <nom>@<version>sur chaque registre. Un paquet inconnu compte comme une version absente. Toute autre erreur arrête la commande : publier à l'aveugle pourrait viser une version qui existe déjà. - Un seul tarball. Si un registre a déjà la version,
npm pack <nom>@<version>télécharge son tarball. Sinon, la commande lancenpm run prepublishOnly(oubuildsans ce script), puisnpm packdans le paquet. Un tarball se publie sans scripts de cycle de vie : les contrôles deprepublishOnlypassent donc ici, une seule fois. npm publish <tarball>vers chaque registre où la version manque.
Les deux registres reçoivent ainsi le même fichier, octet pour octet. Le package-lock.json d'un site vérifie l'empreinte de chaque tarball : c'est ce qui permet à un site de basculer sur le registre de secours sans toucher à son lockfile autrement que pour l'hôte.
Le registre est passé par scope, --@fraza:registry=<adresse>, jamais par --registry : npm choisit une ligne de scope de ~/.npmrc avant --registry, et la machine d'un mainteneur pointe souvent @fraza vers registry.fraza.ca. Les options de ligne de commande passent avant publishConfig.
La commande se relance sans risque : un registre qui a déjà la version est sauté. C'est aussi ainsi qu'une version publiée sur registry.fraza.ca seulement est portée sur npmjs, avec -g : son tarball est téléchargé et envoyé tel quel.
npm peut demander un code à usage unique quand le compte npmjs a la double authentification : la sortie de npm s'affiche telle quelle et le code se saisit dans le terminal.
| Option | Effet |
|---|---|
--dry-run | Interroge les registres et dit où la version partirait, et depuis quel tarball. Ne construit et ne publie rien. |
-g, --global | Ajoute npmjs, en premier, aux registres d'un paquet @fraza/*. npm run release:publish -g fonctionne sans --. |
Dépendances Fraza
La dernière version de chaque paquet Fraza fonctionne avec les autres. Personne ne suit quelle version va avec quelle autre : les paquets Fraza se déclarent entre eux avec "*" (@fraza/icons dans le core, @fraza/core dans les peerDependencies d'un module), et la commande n'écrit aucune plage. Elle construit et teste le paquet contre la dernière version de chacun, quelle que soit celle du lockfile. Un site reçoit la dernière version exacte de chacun par fraza-update.
release-check
npx fraza-module release-checkRépond à une seule question : npm publish peut-il partir maintenant ? La commande ne monte aucune version, ne committe rien, ne crée aucune étiquette et ne publie rien. Elle sert pour une version déjà montée et committée à la main : release fait ses propres vérifications.
- L'arbre de travail git est propre, et l'étiquette
v<version>n'existe pas encore. sync-version --checkpasse.CHANGELOG.mdetchangelog.jsonexistent, sont déclarés dansfiles, etchangelog.jsons'ouvre sur la version depackage.json.package.json#frazaporte le résumé que litfraza-update.publishConfigest conforme au scope du paquet : voirpublish.npm run buildpasse.- Pour un module,
checkpasse, sur ledist/que le build vient d'écrire. npm testpasse, quand le paquet déclare ce script.- L'arbre de travail est toujours propre : un fichier suivi que le build régénère, comme le module de version ou
THIRD-PARTY-NOTICES.md, était périmé dans le commit.
Les étapes 1 à 3 sont vérifiées ensemble, et le build ne part que si elles passent. La procédure complète de publication est dans Publication.
test-clean
npx fraza-module test-clean --dry-run
npx fraza-module test-clean --older-than 6
npx fraza-module test-cleanListe les schémas de test restés sur la base de FRAZA_TEST_DATABASE_URL, avec leur âge lu dans leur nom, puis les supprime. Un processus de test tué par SIGKILL en laisse un : voir Tests.
| Option | Effet |
|---|---|
--older-than <heures> | Ne supprime que les schémas datés plus vieux que ce nombre d'heures. Un schéma au nom sans date, laissé par une version précédente du core, reste. |
--prefix <préfixe> | Les schémas de ce préfixe et des préfixes plus longs qui commencent par lui suivi d'un souligné. fraza_test par défaut. |
--dry-run | Liste les schémas sans rien supprimer. |
Sans --older-than, la commande supprime tous les schémas de test du préfixe, y compris ceux d'une suite en cours : la lancer quand aucun test ne tourne. Seul un nom au format de l'aide aux tests est touché. La commande lit la variable dans l'environnement du terminal, jamais dans un fichier .env, comme l'aide aux tests. Elle sort en 1 si la variable manque, si la base ne répond pas, ou si un schéma n'a pas pu être supprimé.
Preset tsup
Le core exporte la configuration tsup commune, defineFrazaConfig, sous @fraza/core/tools/tsup. Le tsup.config.js d'un module tient en trois lignes :
import { defineFrazaConfig } from '@fraza/core/tools/tsup';
export default defineFrazaConfig();| Option | Défaut | Effet |
|---|---|---|
externals | [] | Paquets que le build ne doit jamais résoudre, en plus de react, react-dom, next et @fraza/core. Un module qui charge sharp les déclare : defineFrazaConfig({ externals: ["sharp"] }). |
srcDir | src | Dossier des sources, relatif à la racine du paquet. |
Le preset compile chaque fichier .js et .jsx du dossier en module autonome, bundle: false, JSX transformé par react/jsx-runtime, outbase sur le dossier des sources. Les fichiers *.test.js sont exclus : un test compilé dans dist/ partirait dans chaque projet. Les raisons de bundle: false sont dans Build et exports.
Le fichier n'importe pas tsup : une configuration tsup est un objet, et tsup reste ainsi une dépendance de développement du paquet. Le core construit lui-même avec ce preset, importé depuis ses sources.
Notes
fraza-module n'est pas fraza-modules. Le second, au pluriel, régénère les manifestes de modules d'un site. Le premier construit un paquet.
La commande travaille sur le paquet du dossier courant, trouvé en remontant jusqu'au premier package.json. Dans un script npm, c'est la racine du paquet. test-clean fait exception : il travaille sur la base de test.
Un module qui utilise fraza-module dépend de la version du core qui l'a introduite. La commande vient du core installé dans node_modules : un core plus ancien ne la fournit pas.