portes-de-parité
Construisez la baseline une fois à partir de capture/, puis vérifiez chaque build jusqu'à ce que toutes les portes passent. Les portes lisent uniquement baseline/, jamais le site en direct, donc une build peut encore être vérifiée après la disparition de l'ancien hôte.
La règle d'or
La build par défaut est égale au site en direct, et une porte n'est jamais assouplie. Aucun indicateur ne transforme un échec en avertissement. Quatre autres règles en découlent :
- Ne modifiez jamais
baseline/à la main. La seule exception :keepdansbaseline/scripts.json, qui permet de supprimer un fournisseur (mettez-le àfalseet enregistrez la décision). - Une différence est une entrée, pas une modification. Inévitable :
exceptions.json. Une amélioration :proposals.json, actif uniquement avecPROPOSALS=on. Les deux sont dansreferences/exceptions.md. - Un saut n'est pas une réussite. Une porte qui n'a pas pu vérifier quelque chose affiche
N ignoré (raison)sur sa ligne de résumé. Signalez chaque saut. - Signalez une porte erronée comme un bug du kit ; ne modifiez jamais la porte dans le site, ni la baseline.
Exécution des scripts
Un site créé par convert-site contient les scripts dans scripts/ ; exécutez-les depuis la racine du site avec les commandes npm ci-dessous, options après --. En dehors d'un site structuré, utilisez la forme node indiquée sous chaque étape depuis la racine du site, où <dossier-du-skill> est le chemin absolu du dossier de cette compétence dans une copie du kit où npm install a été exécuté. Chaque script affiche --help ; exécutez-le plutôt que de lire le source. Code de sortie 0 : réussite, 1 : porte échouée ou dérive, 2 : utilisation ou entrée manquante.
1. Baseline (une fois)
npm run baseline
node <dossier-du-skill>/scripts/build-baseline.mjs --capture capture --out baseline # solution de repli
Elle a besoin de capture/inventory.json, media.json (le media.mjs de la capture) et templates.json (templates.mjs), et se termine par le code 2 en nommant l'étape à exécuter si l'un d'eux manque. Elle écrit baseline/{routes,seo,redirects,media,forms,scripts,meta,counts}.json et baseline/text/, ainsi que redirects.csv à la racine du site. Un lien court sans cible est omis et nommé sur stderr. Chaque saut intermédiaire d'une chaîne capturée obtient sa propre ligne allant directement à la cible finale (/anciens-services → /anciens-services/ → /services/ donne aussi /anciens-services/,/services/, notez saut de /anciens-services), à moins que ce saut ne soit lui-même une route construite, de sorte qu'un lien vers n'importe quelle URL de la chaîne atterrisse en un saut.
Validez baseline/ et redirects.csv ensemble. --check recompte baseline/ par rapport à counts.json (code de sortie 1 en cas de dérive). La forme de chaque fichier est dans references/contracts.md.
2. Redirections
redirects.csv (depuis,vers,statut,source,note) est la source unique des redirections du site. source est crawl, sitemap, shortlink ou seed de la capture ; une ligne que vous ajoutez est decision, sa note nommant l'identifiant de la décision. Ne modifiez jamais manuellement la sortie compilée ; compilez :
npm run redirects:build
node <dossier-du-skill>/scripts/build-redirects.mjs --csv redirects.csv --host vercel --out vercel.json --trailing-slash always # solution de repli
--host cloudflare --out public/_redirects pour Pages (Astro le copie dans dist/). Vercel : la clé redirects est remplacée, toutes les autres clés sont conservées, et trailingSlash est supprimé et écrit comme les propres règles de slash 308 du compilateur après celles du site, de sorte qu'une source sans slash reste en un saut. Seul un chemin se terminant par une extension de fichier statique compte comme un fichier pour ces règles, quelle que soit la casse : les types web, les types de document, média et archive qu'un dossier de téléchargement contient (docx, xlsx, csv, mov, wav, heic, gz, ...), et chaque extension trouvée dans dist/ (sinon public/), donc réexécutez-le après la build ; /guide-mise-a-jour/3.5/ est une page et conserve son slash. Un fichier construit que les règles redirigeraient quand même fait échouer la porte des redirections comme fichier construit redirigé.
Cloudflare : une ligne de requête (/?p=7) ne peut pas être mise en correspondance par _redirects et va dans cloudflare-query-redirects.csv, à créer en tant que redirection unique (un élément "a besoin de vous"). Une ligne de requête dont la cible est son propre chemin (/?p=2 vers /) est une non-opération et est omise sur les deux hôtes.
Une ligne capturée (tout source sauf decision) est une URL en direct et ne contient aucun paramètre : pour Vercel, chaque : et * qu'elle contient est échappé (/foo:bar/ est écrit /foo\:bar/, et de même /trackshttps://www.example.com/x/, un href en direct cassé que la capture a suivi) ; // est conservé. Seule une ligne decision utilise les paramètres :nom et les * splats, et même là, un : avant un chiffre (un port) est échappé. Vercel rejette un : nu, et la simulation des portes fait de même. Une source sans forme valide (pas de / initial, espace blanc, caractère de contrôle ; sur Cloudflare aussi un :<nom> ou * capturé, que _redirects ne peut pas échapper) est supprimée, nommée sur stderr et listée comme { source, reason } dans redirects-skipped.json. Ce fichier se trouve à côté de redirects.csv (la racine du site), jamais sous public/, donc il n'est pas déployé. La porte des redirections et verify-live comptent une ligne listée comme ignorée, jamais réussie. Lorsque le chemin ne doit simplement pas avoir de redirection, enregistrez plutôt une exception "pas de redirection" (section 4) : { "kind": "redirect", "path": "<source>", "reason": "...", "action": "none", "approvedBy": "...", "date": "..." }.
3. Vérification
npm run build
npm run verify -- --endpoint https://formulaires.example.com/f/abc --thank-you /merci/
node <dossier-du-skill>/scripts/verify/run-all.mjs --endpoint https://formulaires.example.com/f/abc --thank-you /merci/ # solution de repli
run-all exécute les dix portes dans l'ordre alphabétique, ne donne à chacune que les options qu'elle accepte, et s'arrête au premier échec. Options partagées : --paths (liste séparée par des virgules, * correspond à tout), --json, --origin (liens, seo, sitemaps : l'hôte pour lequel la build est faite), --allow-extra (routes), --endpoint, --honeypot, --thank-you (formulaires ; --endpoint est requis lorsque la baseline a un formulaire POST), --host, --config (redirections, liens), --csv (redirections), --media-plan (médias). Corrigez le premier échec, reconstruisez, réexécutez. Une seule porte : node <dossier-du-skill>/scripts/verify/<porte>.mjs.
| Porte | Réussit quand | Un saut signifie |
|---|---|---|
forms | Chaque formulaire en direct est sur sa page avec les mêmes noms de champs dans l'ordre et la méthode ; l'action d'un formulaire POST est --endpoint et il porte --honeypot ; un formulaire GET (une boîte de recherche) conserve le chemin d'action du site en direct et n'a besoin ni de l'un ni de l'autre ("N formulaires GET/recherche comparés") ; un formulaire POST dont l'action en direct est sur un autre domaine enregistrable (un hôte de liste de diffusion ; www, http, sous-domaines et gestionnaires WordPress tels que admin-ajax.php ou ?wpcf7 comptent comme le site) est conservé tel quel, comparé comme un formulaire GET par son URL d'action complète, et n'a pas besoin de --endpoint ("N formulaires conservés tels quels (postent vers <hôte>)") | Pas de --thank-you : la page de remerciement des formulaires POST n'a pas été vérifiée |
headers | La configuration de l'hôte envoie nosniff, Referrer-Policy, X-Frame-Options ou CSP frame-ancestors sur chaque chemin, et, quand il n'est pas indexable, X-Robots-Tag: noindex sur chaque chemin ou (Vercel) le meta robots noindex sur chaque page construite ; une règle d'hôte de prévisualisation (https://*.pages.dev/*) compte uniquement pour l'hôte de prévisualisation ; avec PUBLIC_INDEXABLE=true, noindex sur chaque chemin est interdit | |
links | Chaque href et src interne résout en une page ou un fichier construit, une source de redirection ou un média de baseline, ou atterrit sur une page construite en un saut à travers les règles compilées (vercel.json ou dist/_redirects : les règles de slash et chaque ligne de redirects.csv), comme le site en direct l'a redirigé ("N liens résolus via des redirections") ; deux sauts ou une cible manquante font échouer ; sans configuration d'hôte, /a-propos où la build sert /a-propos/ échoue | |
media | Chaque fichier média de la baseline est servi par la build avec les mêmes octets (--paths choisit un fichier par son chemin ou par une page qui le référence) : dans dist/ en mode plan repo ; sous le prefix du plan, dans le manifeste du store (store) ou encore sur le site en direct (live-temporary) ; en dehors du préfixe, dans dist/ dans tous les modes. Avec PUBLIC_INDEXABLE=true, live-temporary échoue pour chaque fichier sous le préfixe (une build de release ne peut pas servir de médias depuis le site en direct ; un plan sans préfixe couvre chaque fichier) et vérifie toujours les fichiers en dehors dans dist/ | Mode plan live-temporary sur une build de staging : les fichiers sous le préfixe sont servis depuis le site en direct, non vérifiés |
redirects | Chaque redirection de la baseline et ligne csv répond en un saut avec le même statut, et la cible répond 200 dans dist/ (ou est externe, ou sous un préfixe dans excluded de baseline/meta.json : alors seul le premier saut est vérifié) ; aucune source n'est aussi un fichier construit ; aucune page ou fichier construit n'est redirigé depuis sa propre URL (page construite redirigée par la règle <règle> ; aucune exception ne la couvre, corrigez la règle) ; un chemin avec une exception "pas de redirection" (kind: "redirect", action: "none") n'est pas du tout redirigé | Une ligne de requête sans opération, toute ligne de requête sur Cloudflare, ou une source sans forme valide que redirects-skipped.json liste avec une raison (non compilé) |
routes | Chaque page, article, archive et flux à 200 est construit, les routes noindex disent encore noindex, rien de supplémentaire n'est construit (les propres scripts de la capture dans baseline/js.json et les feuilles de style dans baseline/css.json à leurs chemins en direct, les noms de sitemap en direct dans baseline/sitemaps.json, et le jeu d'icônes de lancement ne sont pas des suppléments) ; une route capturée à un statut autre que 200, 3xx, 404 ou 410 échoue comme non capturée : <statut> à moins qu'une exception routes ne l'ignore ou ne nomme son remplacement construit | Une exception routes avec to: "skip" : cette route n'a jamais été capturée |
scripts | Chaque fournisseur conservé est exactement sur ses pages en direct avec les mêmes identifiants ; un fournisseur supprimé sur aucune ; aucun nouveau fournisseur | |
seo | Titre, description, canonical, robots, lang, h1, h2, og et @types JSON-LD égaux par page ; URLs du site sur l'origine du site. Sauf si PUBLIC_INDEXABLE=true, robots doit porter le noindex, nofollow de staging et le reste est égal à celui du site en direct après ces deux ; une build de release compare robots exactement | |
sitemaps | Chaque sitemap de premier niveau dans dist/ (le sitemap-index.xml d'Astro, ou un nom en direct dans baseline/sitemaps.json tel que wp-sitemap.xml ou sitemap_index.xml) liste exactement les pages 200 indexables sur l'origine (les échappements de pourcentage sont comparés sans distinction de casse : %c3 est %C3) ; chaque ligne Sitemap: du robots.txt est sur l'origine et nomme un fichier dans dist/, ou est une ligne que le robots.txt en direct (capture/robots.txt) portait textuellement ; chaque flux est construit avec autant d'éléments | |
text | Le texte visible de chaque page est égal à baseline/text/ (différence de mots, 5 mots de contexte) |
Une capture faite avec --exclude enregistre ses préfixes de chemin dans excluded de baseline/meta.json : la porte links ne vérifie pas un lien sous l'un d'eux (comme un lien vers un autre hôte, noté "N liens sous chemins exclus ... non vérifiés"), et la porte sitemaps accepte une ligne Sitemap: du robots.txt sous l'un d'eux lorsque le robots.txt en direct la portait textuellement (le sitemap d'un sous-site multisite sur le même hôte).
4. Différences et décisions
Lorsque la build ne peut pas ou ne doit pas correspondre au site en direct, lisez references/exceptions.md. En bref : une entrée est { path, field, from, to, reason, approvedBy, date } ; from: "" enregistre une valeur que la build ajoute, to: "" une qu'elle supprime ; une exception qui ne correspond plus, ou qui nomme un chemin que la porte ne vérifie pas, fait échouer la porte. Chaque bizarrerie sur le site en direct (un H1 manquant, une balise morte, un lien cassé) est reproduite par défaut et obtient une ligne de décision (id, sujet, choix, défaut, raison, coût si erreur) le disant et ce que serait la correction.
Un enregistrement a un kind au lieu d'un field : { "kind": "redirect", "path": "<source>", "reason": "...", "action": "none", "approvedBy": "...", "date": "..." } signifie "pas de redirection pour ce chemin, intentionnellement" (une redirection en direct enregistrée pour un href cassé qui ne devrait rien rediriger). Chaque clé est requise : cela supprime un comportement en direct, donc c'est signé comme toute exception. La porte des redirections demande le chemin et ne le fait passer que lorsque les règles ne le redirigent pas ; sur un chemin que la porte ne vérifie pas, elle échoue comme chemin non vérifié ; verify-live l'ignore avec la raison. Toutes les autres portes l'ignorent.
5. Staging, propositions et builds de release
PUBLIC_INDEXABLE et PROPOSALS changent ce qu'Astro construit, donc définissez-les sur la build, pas seulement sur l'exécution de la porte ; une porte exécutée sur une build faite sans eux vérifie le mauvais site. Passez --origin uniquement lorsque la build elle-même a été faite pour cet hôte ; le site d'une build structurée est l'origine en direct, donc ses canoniques et sitemap restent dessus.
npm run build && npm run verify -- --endpoint ... --thank-you ... # staging
PROPOSALS=on npm run build && npm run verify:proposals -- --endpoint ... --thank-you ... # propositions
PUBLIC_INDEXABLE=true npm run build && npm run verify:release -- --endpoint ... --thank-you ... # release
Après un déploiement de staging, vérifiez-le via HTTP par rapport à la baseline. Un déploiement de release est vérifié de la même manière sans --expect-noindex :
npm run verify:live -- --base https://mon-site.vercel.app --expect-noindex
node <dossier-du-skill>/scripts/verify-live.mjs --base https://mon-site.vercel.app --expect-noindex # solution de repli
--bypass "$VERCEL_AUTOMATION_BYPASS_SECRET" s'applique uniquement lorsque le staging a été délibérément protégé (la valeur de contournement depuis l'environnement, jamais collée dans le chat) ; un alias de staging public n'en a pas besoin.
Il vérifie chaque route de baseline à 200 (ou un --sample), chaque redirection en un saut avec le même type de statut, noindex sur les pages si et seulement si --expect-noindex, le sitemap, et 20 fichiers média par octets (ou --media-manifest, dont l'url peut être l'URL absolue du store ou un chemin de site vérifié à <base><chemin>). Une redirection listée dans redirects-skipped.json ou couverte par une exception "pas de redirection" (--skipped, --exceptions ; les deux lus depuis la racine du site par défaut) est ignorée et nommée avec sa raison. Chaque requête expire après 30 s, un échec de cette vérification plutôt qu'un blocage. Le code de sortie 3 signifie qu'il a été bloqué : attendez et réexécutez. Le code de sortie 3 avec protection de déploiement: signifie que Vercel a répondu avec sa redirection de connexion (302 vers vercel.com/sso-api) : utilisez l'alias de production du projet (public par défaut) plutôt que l'URL de déploiement brute ; uniquement lorsque le staging a été délibérément protégé, le drapeau --bypass peut être utilisé avec le secret de contournement d'automation.