@cogenta/starters 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/blueprints/assets/photos/association/coat-bank.jpg +0 -0
- package/dist/blueprints/assets/photos/association/food-bank.jpg +0 -0
- package/dist/blueprints/assets/photos/association/garden-corner.jpg +0 -0
- package/dist/blueprints/assets/photos/association/garden-planting.jpg +0 -0
- package/dist/blueprints/assets/photos/association/hall.jpg +0 -0
- package/dist/blueprints/assets/photos/association/hero.jpg +0 -0
- package/dist/blueprints/assets/photos/association/homework-club.jpg +0 -0
- package/dist/blueprints/assets/photos/association/orientation.jpg +0 -0
- package/dist/blueprints/assets/photos/association/partner-fr-college.png +0 -0
- package/dist/blueprints/assets/photos/association/partner-fr-delcourt.png +0 -0
- package/dist/blueprints/assets/photos/association/partner-fr-fondation.png +0 -0
- package/dist/blueprints/assets/photos/association/partner-fr-habitat.png +0 -0
- package/dist/blueprints/assets/photos/association/partner-fr-mairie.png +0 -0
- package/dist/blueprints/assets/photos/association/partner-fr-pharmacie.png +0 -0
- package/dist/blueprints/assets/photos/association/supper.jpg +0 -0
- package/dist/blueprints/assets/photos/blog/desk-setup.jpg +0 -0
- package/dist/blueprints/assets/photos/blog/library.jpg +0 -0
- package/dist/blueprints/assets/photos/blog/notebook-and-coffee.jpg +0 -0
- package/dist/blueprints/assets/photos/blog/notebooks.jpg +0 -0
- package/dist/blueprints/assets/photos/blog/platform.jpg +0 -0
- package/dist/blueprints/assets/photos/blog/pour-over.jpg +0 -0
- package/dist/blueprints/assets/photos/blog/typing.jpg +0 -0
- package/dist/blueprints/assets/photos/documentation/delivery-flow-en.png +0 -0
- package/dist/blueprints/assets/photos/documentation/delivery-flow-fr.png +0 -0
- package/dist/blueprints/assets/photos/magazine/allotments.jpg +0 -0
- package/dist/blueprints/assets/photos/magazine/archive-stacks.jpg +0 -0
- package/dist/blueprints/assets/photos/magazine/baguettes.jpg +0 -0
- package/dist/blueprints/assets/photos/magazine/dining-room.jpg +0 -0
- package/dist/blueprints/assets/photos/magazine/empty-tram-stop.jpg +0 -0
- package/dist/blueprints/assets/photos/magazine/gallery.jpg +0 -0
- package/dist/blueprints/assets/photos/magazine/office-block-dusk.jpg +0 -0
- package/dist/blueprints/assets/photos/magazine/reading-room.jpg +0 -0
- package/dist/blueprints/assets/photos/magazine/reception-hall.jpg +0 -0
- package/dist/blueprints/assets/photos/magazine/theatre.jpg +0 -0
- package/dist/blueprints/assets/photos/magazine/town-hall.jpg +0 -0
- package/dist/blueprints/assets/photos/magazine/tram-stop.jpg +0 -0
- package/dist/blueprints/assets/photos/portfolio/coast-boats.jpg +0 -0
- package/dist/blueprints/assets/photos/portfolio/coast-cover.jpg +0 -0
- package/dist/blueprints/assets/photos/portfolio/coast-ridge.jpg +0 -0
- package/dist/blueprints/assets/photos/portfolio/coast-water.jpg +0 -0
- package/dist/blueprints/assets/photos/portfolio/forest-beech.jpg +0 -0
- package/dist/blueprints/assets/photos/portfolio/forest-birch.jpg +0 -0
- package/dist/blueprints/assets/photos/portfolio/forest-branches.jpg +0 -0
- package/dist/blueprints/assets/photos/portfolio/forest-cover.jpg +0 -0
- package/dist/blueprints/assets/photos/portfolio/glacier-cave.jpg +0 -0
- package/dist/blueprints/assets/photos/portfolio/glacier-cover.jpg +0 -0
- package/dist/blueprints/assets/photos/portfolio/glacier-melt.jpg +0 -0
- package/dist/blueprints/assets/photos/portfolio/glacier-tongue.jpg +0 -0
- package/dist/blueprints/assets/photos/portfolio/lagoon-cover.jpg +0 -0
- package/dist/blueprints/assets/photos/portfolio/lagoon-egret.jpg +0 -0
- package/dist/blueprints/assets/photos/portfolio/lagoon-shore.jpg +0 -0
- package/dist/blueprints/assets/photos/portfolio/lagoon-storm.jpg +0 -0
- package/dist/blueprints/assets/photos/portfolio/port-cover.jpg +0 -0
- package/dist/blueprints/assets/photos/portfolio/port-dusk.jpg +0 -0
- package/dist/blueprints/assets/photos/portfolio/port-marina.jpg +0 -0
- package/dist/blueprints/assets/photos/portfolio/port-quay.jpg +0 -0
- package/dist/blueprints/assets/photos/portfolio/towers-cover.jpg +0 -0
- package/dist/blueprints/assets/photos/portfolio/towers-green.jpg +0 -0
- package/dist/blueprints/assets/photos/portfolio/towers-single.jpg +0 -0
- package/dist/blueprints/assets/photos/portfolio/towers-valley.jpg +0 -0
- package/dist/blueprints/assets/photos/portfolio/vines-cover.jpg +0 -0
- package/dist/blueprints/assets/photos/portfolio/vines-grapes.jpg +0 -0
- package/dist/blueprints/assets/photos/portfolio/vines-shed.jpg +0 -0
- package/dist/blueprints/assets/photos/portfolio/vines-tree.jpg +0 -0
- package/dist/blueprints/assets/photos/restaurant/beetroot-carpaccio.jpg +0 -0
- package/dist/blueprints/assets/photos/restaurant/chocolate-tart.jpg +0 -0
- package/dist/blueprints/assets/photos/restaurant/cod-vin-jaune.jpg +0 -0
- package/dist/blueprints/assets/photos/restaurant/duck-confit.jpg +0 -0
- package/dist/blueprints/assets/photos/restaurant/grilled-octopus.jpg +0 -0
- package/dist/blueprints/assets/photos/restaurant/hero.jpg +0 -0
- package/dist/blueprints/assets/photos/restaurant/market-stall.jpg +0 -0
- package/dist/blueprints/assets/photos/restaurant/mushroom-risotto.jpg +0 -0
- package/dist/blueprints/assets/photos/restaurant/pike-quenelle.jpg +0 -0
- package/dist/blueprints/assets/photos/restaurant/poached-pear.jpg +0 -0
- package/dist/blueprints/assets/photos/restaurant/praline-cake.jpg +0 -0
- package/dist/blueprints/assets/photos/restaurant/pumpkin-soup.jpg +0 -0
- package/dist/blueprints/assets/photos/restaurant/street-burdeau.jpg +0 -0
- package/dist/blueprints/assets/photos/saas/api-webhooks-en.png +0 -0
- package/dist/blueprints/assets/photos/saas/api-webhooks-fr.png +0 -0
- package/dist/blueprints/assets/photos/saas/approvals-queue-en.png +0 -0
- package/dist/blueprints/assets/photos/saas/approvals-queue-fr.png +0 -0
- package/dist/blueprints/assets/photos/saas/audit-log-en.png +0 -0
- package/dist/blueprints/assets/photos/saas/audit-log-fr.png +0 -0
- package/dist/blueprints/assets/photos/saas/erp-sync-en.png +0 -0
- package/dist/blueprints/assets/photos/saas/erp-sync-fr.png +0 -0
- package/dist/blueprints/assets/photos/saas/logo-castellan-alimentaire.png +0 -0
- package/dist/blueprints/assets/photos/saas/logo-cliniques-du-vivier.png +0 -0
- package/dist/blueprints/assets/photos/saas/logo-marenne-logistique.png +0 -0
- package/dist/blueprints/assets/photos/saas/logo-nordgate-habitat.png +0 -0
- package/dist/blueprints/assets/photos/saas/logo-tessaly-energies.png +0 -0
- package/dist/blueprints/assets/photos/saas/office-london.jpg +0 -0
- package/dist/blueprints/assets/photos/saas/office-montreal.jpg +0 -0
- package/dist/blueprints/assets/photos/saas/office-paris.jpg +0 -0
- package/dist/blueprints/assets/photos/saas/office-toronto.jpg +0 -0
- package/dist/blueprints/assets/photos/saas/policy-editor-en.png +0 -0
- package/dist/blueprints/assets/photos/saas/policy-editor-fr.png +0 -0
- package/dist/blueprints/assets/photos/saas/security-racks.jpg +0 -0
- package/dist/blueprints/assets/photos/saas/spend-reporting-en.png +0 -0
- package/dist/blueprints/assets/photos/saas/spend-reporting-fr.png +0 -0
- package/dist/blueprints/assets/photos/saas/sso-scim-en.png +0 -0
- package/dist/blueprints/assets/photos/saas/sso-scim-fr.png +0 -0
- package/dist/blueprints/assets/photos/store/aleppo-soap.jpg +0 -0
- package/dist/blueprints/assets/photos/store/copper-pan.jpg +0 -0
- package/dist/blueprints/assets/photos/store/crochet-throw.jpg +0 -0
- package/dist/blueprints/assets/photos/store/enamel-mug.jpg +0 -0
- package/dist/blueprints/assets/photos/store/hero.jpg +0 -0
- package/dist/blueprints/assets/photos/store/knit-sweater.jpg +0 -0
- package/dist/blueprints/assets/photos/store/leather-boots.jpg +0 -0
- package/dist/blueprints/assets/photos/store/leather-duffel.jpg +0 -0
- package/dist/blueprints/assets/photos/store/leather-wallet.jpg +0 -0
- package/dist/blueprints/assets/photos/store/moka-pot.jpg +0 -0
- package/dist/blueprints/assets/photos/store/skillet.jpg +0 -0
- package/dist/blueprints/assets/photos/store/stoneware-bottles.jpg +0 -0
- package/dist/blueprints/assets/photos/store/story.jpg +0 -0
- package/dist/blueprints/assets/photos/store/turned-bowl.jpg +0 -0
- package/dist/blueprints/association-copy-en.d.ts +3 -0
- package/dist/blueprints/association-copy-en.d.ts.map +1 -0
- package/dist/blueprints/association-copy-en.js +834 -0
- package/dist/blueprints/association-copy-en.js.map +1 -0
- package/dist/blueprints/association-copy-fr.d.ts +3 -0
- package/dist/blueprints/association-copy-fr.d.ts.map +1 -0
- package/dist/blueprints/association-copy-fr.js +841 -0
- package/dist/blueprints/association-copy-fr.js.map +1 -0
- package/dist/blueprints/association-copy.d.ts +301 -0
- package/dist/blueprints/association-copy.d.ts.map +1 -0
- package/dist/blueprints/association-copy.js +17 -0
- package/dist/blueprints/association-copy.js.map +1 -0
- package/dist/blueprints/association-credits.d.ts +16 -0
- package/dist/blueprints/association-credits.d.ts.map +1 -0
- package/dist/blueprints/association-credits.js +80 -0
- package/dist/blueprints/association-credits.js.map +1 -0
- package/dist/blueprints/association-typography.d.ts +23 -0
- package/dist/blueprints/association-typography.d.ts.map +1 -0
- package/dist/blueprints/association-typography.js +55 -0
- package/dist/blueprints/association-typography.js.map +1 -0
- package/dist/blueprints/association.d.ts +86 -287
- package/dist/blueprints/association.d.ts.map +1 -1
- package/dist/blueprints/association.js +503 -1078
- package/dist/blueprints/association.js.map +1 -1
- package/dist/blueprints/blog-copy-en.d.ts +4 -0
- package/dist/blueprints/blog-copy-en.d.ts.map +1 -0
- package/dist/blueprints/blog-copy-en.js +701 -0
- package/dist/blueprints/blog-copy-en.js.map +1 -0
- package/dist/blueprints/blog-copy-fr.d.ts +12 -0
- package/dist/blueprints/blog-copy-fr.d.ts.map +1 -0
- package/dist/blueprints/blog-copy-fr.js +722 -0
- package/dist/blueprints/blog-copy-fr.js.map +1 -0
- package/dist/blueprints/blog-copy.d.ts +157 -0
- package/dist/blueprints/blog-copy.d.ts.map +1 -0
- package/dist/blueprints/blog-copy.js +2 -0
- package/dist/blueprints/blog-copy.js.map +1 -0
- package/dist/blueprints/blog-credits.d.ts +28 -0
- package/dist/blueprints/blog-credits.d.ts.map +1 -0
- package/dist/blueprints/blog-credits.js +73 -0
- package/dist/blueprints/blog-credits.js.map +1 -0
- package/dist/blueprints/blog-rich.d.ts +19 -0
- package/dist/blueprints/blog-rich.d.ts.map +1 -0
- package/dist/blueprints/blog-rich.js +51 -0
- package/dist/blueprints/blog-rich.js.map +1 -0
- package/dist/blueprints/blog.d.ts +51 -176
- package/dist/blueprints/blog.d.ts.map +1 -1
- package/dist/blueprints/blog.js +358 -947
- package/dist/blueprints/blog.js.map +1 -1
- package/dist/blueprints/content-pack.d.ts.map +1 -1
- package/dist/blueprints/content-pack.js +11 -16
- package/dist/blueprints/content-pack.js.map +1 -1
- package/dist/blueprints/content-packs.d.ts +2 -4
- package/dist/blueprints/content-packs.d.ts.map +1 -1
- package/dist/blueprints/content-packs.js +27 -15
- package/dist/blueprints/content-packs.js.map +1 -1
- package/dist/blueprints/documentation-copy-en.d.ts +3 -0
- package/dist/blueprints/documentation-copy-en.d.ts.map +1 -0
- package/dist/blueprints/documentation-copy-en.js +883 -0
- package/dist/blueprints/documentation-copy-en.js.map +1 -0
- package/dist/blueprints/documentation-copy-fr.d.ts +3 -0
- package/dist/blueprints/documentation-copy-fr.d.ts.map +1 -0
- package/dist/blueprints/documentation-copy-fr.js +939 -0
- package/dist/blueprints/documentation-copy-fr.js.map +1 -0
- package/dist/blueprints/documentation-copy.d.ts +52 -0
- package/dist/blueprints/documentation-copy.d.ts.map +1 -0
- package/dist/blueprints/documentation-copy.js +2 -0
- package/dist/blueprints/documentation-copy.js.map +1 -0
- package/dist/blueprints/documentation-writer.d.ts +67 -0
- package/dist/blueprints/documentation-writer.d.ts.map +1 -0
- package/dist/blueprints/documentation-writer.js +190 -0
- package/dist/blueprints/documentation-writer.js.map +1 -0
- package/dist/blueprints/documentation.d.ts +26 -29
- package/dist/blueprints/documentation.d.ts.map +1 -1
- package/dist/blueprints/documentation.js +106 -1064
- package/dist/blueprints/documentation.js.map +1 -1
- package/dist/blueprints/magazine-copy-en.d.ts +13 -0
- package/dist/blueprints/magazine-copy-en.d.ts.map +1 -0
- package/dist/blueprints/magazine-copy-en.js +553 -0
- package/dist/blueprints/magazine-copy-en.js.map +1 -0
- package/dist/blueprints/magazine-copy-fr.d.ts +14 -0
- package/dist/blueprints/magazine-copy-fr.d.ts.map +1 -0
- package/dist/blueprints/magazine-copy-fr.js +568 -0
- package/dist/blueprints/magazine-copy-fr.js.map +1 -0
- package/dist/blueprints/magazine-copy.d.ts +166 -0
- package/dist/blueprints/magazine-copy.d.ts.map +1 -0
- package/dist/blueprints/magazine-copy.js +58 -0
- package/dist/blueprints/magazine-copy.js.map +1 -0
- package/dist/blueprints/magazine-credits.d.ts +19 -0
- package/dist/blueprints/magazine-credits.d.ts.map +1 -0
- package/dist/blueprints/magazine-credits.js +109 -0
- package/dist/blueprints/magazine-credits.js.map +1 -0
- package/dist/blueprints/magazine.d.ts +30 -78
- package/dist/blueprints/magazine.d.ts.map +1 -1
- package/dist/blueprints/magazine.js +269 -690
- package/dist/blueprints/magazine.js.map +1 -1
- package/dist/blueprints/portfolio-copy-en.d.ts +4 -0
- package/dist/blueprints/portfolio-copy-en.d.ts.map +1 -0
- package/dist/blueprints/portfolio-copy-en.js +745 -0
- package/dist/blueprints/portfolio-copy-en.js.map +1 -0
- package/dist/blueprints/portfolio-copy-fr.d.ts +9 -0
- package/dist/blueprints/portfolio-copy-fr.d.ts.map +1 -0
- package/dist/blueprints/portfolio-copy-fr.js +737 -0
- package/dist/blueprints/portfolio-copy-fr.js.map +1 -0
- package/dist/blueprints/portfolio-copy.d.ts +204 -0
- package/dist/blueprints/portfolio-copy.d.ts.map +1 -0
- package/dist/blueprints/portfolio-copy.js +15 -0
- package/dist/blueprints/portfolio-copy.js.map +1 -0
- package/dist/blueprints/portfolio-credits.d.ts +18 -0
- package/dist/blueprints/portfolio-credits.d.ts.map +1 -0
- package/dist/blueprints/portfolio-credits.js +239 -0
- package/dist/blueprints/portfolio-credits.js.map +1 -0
- package/dist/blueprints/portfolio-typography.d.ts +19 -0
- package/dist/blueprints/portfolio-typography.d.ts.map +1 -0
- package/dist/blueprints/portfolio-typography.js +41 -0
- package/dist/blueprints/portfolio-typography.js.map +1 -0
- package/dist/blueprints/portfolio.d.ts +68 -157
- package/dist/blueprints/portfolio.d.ts.map +1 -1
- package/dist/blueprints/portfolio.js +475 -727
- package/dist/blueprints/portfolio.js.map +1 -1
- package/dist/blueprints/restaurant-copy-en.d.ts +3 -0
- package/dist/blueprints/restaurant-copy-en.d.ts.map +1 -0
- package/dist/blueprints/restaurant-copy-en.js +641 -0
- package/dist/blueprints/restaurant-copy-en.js.map +1 -0
- package/dist/blueprints/restaurant-copy-fr.d.ts +3 -0
- package/dist/blueprints/restaurant-copy-fr.d.ts.map +1 -0
- package/dist/blueprints/restaurant-copy-fr.js +665 -0
- package/dist/blueprints/restaurant-copy-fr.js.map +1 -0
- package/dist/blueprints/restaurant-copy.d.ts +234 -0
- package/dist/blueprints/restaurant-copy.d.ts.map +1 -0
- package/dist/blueprints/restaurant-copy.js +15 -0
- package/dist/blueprints/restaurant-copy.js.map +1 -0
- package/dist/blueprints/restaurant-credits.d.ts +18 -0
- package/dist/blueprints/restaurant-credits.d.ts.map +1 -0
- package/dist/blueprints/restaurant-credits.js +121 -0
- package/dist/blueprints/restaurant-credits.js.map +1 -0
- package/dist/blueprints/restaurant.d.ts +55 -128
- package/dist/blueprints/restaurant.d.ts.map +1 -1
- package/dist/blueprints/restaurant.js +425 -770
- package/dist/blueprints/restaurant.js.map +1 -1
- package/dist/blueprints/saas-copy-en.d.ts +3 -0
- package/dist/blueprints/saas-copy-en.d.ts.map +1 -0
- package/dist/blueprints/saas-copy-en.js +901 -0
- package/dist/blueprints/saas-copy-en.js.map +1 -0
- package/dist/blueprints/saas-copy-fr.d.ts +3 -0
- package/dist/blueprints/saas-copy-fr.d.ts.map +1 -0
- package/dist/blueprints/saas-copy-fr.js +912 -0
- package/dist/blueprints/saas-copy-fr.js.map +1 -0
- package/dist/blueprints/saas-copy.d.ts +266 -0
- package/dist/blueprints/saas-copy.d.ts.map +1 -0
- package/dist/blueprints/saas-copy.js +18 -0
- package/dist/blueprints/saas-copy.js.map +1 -0
- package/dist/blueprints/saas-credits.d.ts +22 -0
- package/dist/blueprints/saas-credits.d.ts.map +1 -0
- package/dist/blueprints/saas-credits.js +44 -0
- package/dist/blueprints/saas-credits.js.map +1 -0
- package/dist/blueprints/saas.d.ts +59 -203
- package/dist/blueprints/saas.d.ts.map +1 -1
- package/dist/blueprints/saas.js +505 -1108
- package/dist/blueprints/saas.js.map +1 -1
- package/dist/blueprints/store-copy-en.d.ts +4 -0
- package/dist/blueprints/store-copy-en.d.ts.map +1 -0
- package/dist/blueprints/store-copy-en.js +521 -0
- package/dist/blueprints/store-copy-en.js.map +1 -0
- package/dist/blueprints/store-copy-fr.d.ts +4 -0
- package/dist/blueprints/store-copy-fr.d.ts.map +1 -0
- package/dist/blueprints/store-copy-fr.js +521 -0
- package/dist/blueprints/store-copy-fr.js.map +1 -0
- package/dist/blueprints/store-copy.d.ts +219 -0
- package/dist/blueprints/store-copy.d.ts.map +1 -0
- package/dist/blueprints/store-copy.js +16 -0
- package/dist/blueprints/store-copy.js.map +1 -0
- package/dist/blueprints/store-credits.d.ts +3 -0
- package/dist/blueprints/store-credits.d.ts.map +1 -0
- package/dist/blueprints/store-credits.js +117 -0
- package/dist/blueprints/store-credits.js.map +1 -0
- package/dist/blueprints/store.d.ts +43 -231
- package/dist/blueprints/store.d.ts.map +1 -1
- package/dist/blueprints/store.js +466 -789
- package/dist/blueprints/store.js.map +1 -1
- package/dist/blueprints/vitrine-copy-en.d.ts.map +1 -1
- package/dist/blueprints/vitrine-copy-en.js +1 -0
- package/dist/blueprints/vitrine-copy-en.js.map +1 -1
- package/dist/blueprints/vitrine-copy-fr.d.ts.map +1 -1
- package/dist/blueprints/vitrine-copy-fr.js +1 -0
- package/dist/blueprints/vitrine-copy-fr.js.map +1 -1
- package/dist/blueprints/vitrine-copy.d.ts +1 -0
- package/dist/blueprints/vitrine-copy.d.ts.map +1 -1
- package/dist/blueprints/vitrine.d.ts +1 -0
- package/dist/blueprints/vitrine.d.ts.map +1 -1
- package/dist/blueprints/vitrine.js +24 -3
- package/dist/blueprints/vitrine.js.map +1 -1
- package/package.json +10 -10
- package/dist/blueprints/assets/photos/association/fundraising-dinner.jpg +0 -0
- package/dist/blueprints/assets/photos/association/volunteer-orientation.jpg +0 -0
- package/dist/blueprints/assets/photos/association/volunteer-portrait.jpg +0 -0
- package/dist/blueprints/assets/photos/association/winter-coat-collection.jpg +0 -0
- package/dist/blueprints/assets/photos/documentation/delivery-flow.png +0 -0
- package/dist/blueprints/assets/photos/magazine/archive-room.jpg +0 -0
- package/dist/blueprints/assets/photos/magazine/banquet-hall.jpg +0 -0
- package/dist/blueprints/assets/photos/magazine/city-hall.jpg +0 -0
- package/dist/blueprints/assets/photos/magazine/downtown-towers.jpg +0 -0
- package/dist/blueprints/assets/photos/magazine/gallery-opening.jpg +0 -0
- package/dist/blueprints/assets/photos/magazine/garden-volunteers.jpg +0 -0
- package/dist/blueprints/assets/photos/magazine/station-platform.jpg +0 -0
- package/dist/blueprints/assets/photos/magazine/theater-stage.jpg +0 -0
- package/dist/blueprints/assets/photos/portfolio/fenmore-branch.jpg +0 -0
- package/dist/blueprints/assets/photos/portfolio/fenmore-cover.jpg +0 -0
- package/dist/blueprints/assets/photos/portfolio/fenmore-stationery.jpg +0 -0
- package/dist/blueprints/assets/photos/portfolio/gannet-cover.jpg +0 -0
- package/dist/blueprints/assets/photos/portfolio/gannet-sardines.jpg +0 -0
- package/dist/blueprints/assets/photos/portfolio/gannet-shelf.jpg +0 -0
- package/dist/blueprints/assets/photos/portfolio/hollis-crane-cover.jpg +0 -0
- package/dist/blueprints/assets/photos/portfolio/hollis-crane-grid.jpg +0 -0
- package/dist/blueprints/assets/photos/portfolio/hollis-crane-salt-year.jpg +0 -0
- package/dist/blueprints/assets/photos/portfolio/hollis-crane-spines.jpg +0 -0
- package/dist/blueprints/assets/photos/portfolio/rookery-hall-brochure.jpg +0 -0
- package/dist/blueprints/assets/photos/portfolio/rookery-hall-cover.jpg +0 -0
- package/dist/blueprints/assets/photos/portfolio/rookery-hall-poster-carnival.jpg +0 -0
- package/dist/blueprints/assets/photos/portfolio/rookery-hall-poster-sibelius.jpg +0 -0
- package/dist/blueprints/assets/photos/portfolio/rookery-hall-series.jpg +0 -0
- package/dist/blueprints/assets/photos/portfolio/saltmarsh-quay-cover.jpg +0 -0
- package/dist/blueprints/assets/photos/portfolio/saltmarsh-quay-gate.jpg +0 -0
- package/dist/blueprints/assets/photos/portfolio/saltmarsh-quay-pictograms.jpg +0 -0
- package/dist/blueprints/assets/photos/portfolio/saltmarsh-quay-totem.jpg +0 -0
- package/dist/blueprints/assets/photos/portfolio/tidewater-cover.jpg +0 -0
- package/dist/blueprints/assets/photos/portfolio/tidewater-data.jpg +0 -0
- package/dist/blueprints/assets/photos/portfolio/tidewater-report-cover.jpg +0 -0
- package/dist/blueprints/assets/photos/portfolio/warp-and-weft-cover.jpg +0 -0
- package/dist/blueprints/assets/photos/portfolio/warp-and-weft-room.jpg +0 -0
- package/dist/blueprints/assets/photos/portfolio/westmoor-rail-components.jpg +0 -0
- package/dist/blueprints/assets/photos/portfolio/westmoor-rail-cover.jpg +0 -0
- package/dist/blueprints/assets/photos/portfolio/westmoor-rail-departures.jpg +0 -0
- package/dist/blueprints/assets/photos/restaurant/beetroot-goats-curd.jpg +0 -0
- package/dist/blueprints/assets/photos/restaurant/ceps-risotto.jpg +0 -0
- package/dist/blueprints/assets/photos/restaurant/duck-leg-red-cabbage.jpg +0 -0
- package/dist/blueprints/assets/photos/restaurant/hake-green-peppercorns.jpg +0 -0
- package/dist/blueprints/assets/photos/restaurant/vegetable-broth.jpg +0 -0
- package/dist/blueprints/assets/photos/saas/adrian-tan.jpg +0 -0
- package/dist/blueprints/assets/photos/saas/api-webhooks.png +0 -0
- package/dist/blueprints/assets/photos/saas/approvals-queue.png +0 -0
- package/dist/blueprints/assets/photos/saas/audit-log.png +0 -0
- package/dist/blueprints/assets/photos/saas/erp-sync.png +0 -0
- package/dist/blueprints/assets/photos/saas/policy-editor.png +0 -0
- package/dist/blueprints/assets/photos/saas/spend-reporting.png +0 -0
- package/dist/blueprints/assets/photos/saas/sso-scim.png +0 -0
- package/dist/blueprints/assets/photos/store/brushed-cotton-overshirt.jpg +0 -0
- package/dist/blueprints/assets/photos/store/cable-knit-beanie.jpg +0 -0
- package/dist/blueprints/assets/photos/store/canvas-shoulder-bag.jpg +0 -0
- package/dist/blueprints/assets/photos/store/canvas-tote.jpg +0 -0
- package/dist/blueprints/assets/photos/store/cast-iron-skillet.jpg +0 -0
- package/dist/blueprints/assets/photos/store/field-jacket.jpg +0 -0
- package/dist/blueprints/assets/photos/store/heavyweight-t-shirt.jpg +0 -0
- package/dist/blueprints/assets/photos/store/leather-card-holder.jpg +0 -0
- package/dist/blueprints/assets/photos/store/linen-table-runner.jpg +0 -0
- package/dist/blueprints/assets/photos/store/porcelain-pour-over-set.jpg +0 -0
- package/dist/blueprints/assets/photos/store/striped-wool-blanket.jpg +0 -0
|
@@ -0,0 +1,939 @@
|
|
|
1
|
+
import { deepFrenchTypography, frenchTypography } from './documentation-writer.js';
|
|
2
|
+
/**
|
|
3
|
+
* La documentation du produit, en français. Voir `documentation-copy.ts`
|
|
4
|
+
* pour la forme.
|
|
5
|
+
*
|
|
6
|
+
* Le texte est écrit avec des espaces ordinaires : `writer` (et
|
|
7
|
+
* `deepFrenchTypography` pour le reste) y met les espaces insécables avant
|
|
8
|
+
* `: ; ? ! %` et dans les guillemets. Les commandes, clés de configuration,
|
|
9
|
+
* noms d’API et sorties de terminal restent en anglais, comme dans toute
|
|
10
|
+
* documentation française d’un outil de développement ; les commentaires
|
|
11
|
+
* des exemples de code sont traduits.
|
|
12
|
+
*/
|
|
13
|
+
const BLOCK_VERSION = '1.0.0';
|
|
14
|
+
const DOC_PAGES_ONLY = {
|
|
15
|
+
pages: { mode: 'only', targets: [{ kind: 'collection', collection: 'doc_page' }] },
|
|
16
|
+
};
|
|
17
|
+
const PAGES = [
|
|
18
|
+
{
|
|
19
|
+
title: 'Introduction',
|
|
20
|
+
slug: 'introduction',
|
|
21
|
+
section: 'start',
|
|
22
|
+
summary: 'Ce que fait %N%, où il s’exécute à côté de votre application, et par où poursuivre la lecture.',
|
|
23
|
+
body: (w) => [
|
|
24
|
+
w.p('%N% livre les webhooks de votre application. Vous lui envoyez un événement en HTTP ; il signe une copie pour chaque point de terminaison abonné à ce type d’événement, envoie chacune d’elles, enregistre chaque tentative et réessaie les échecs selon un calendrier fixe, jusqu’au succès ou à l’épuisement des tentatives.'),
|
|
25
|
+
w.p('Il s’exécute comme un seul processus Node.js, à côté d’une base de données : SQLite sur un poste de développement, PostgreSQL en production. Aucun compte hébergé à créer, aucune file de messages à exploiter.'),
|
|
26
|
+
w.h2('Ce qu’il prend en charge'),
|
|
27
|
+
w.ul([
|
|
28
|
+
'**La signature.** Chaque requête porte un en-tête `%HDR%-Signature` que le destinataire vérifie en quelques lignes de code.',
|
|
29
|
+
'**Les réessais.** Une livraison en échec est retentée après 30 secondes, 2 minutes, 10 minutes, 1 heure, 6 heures et 24 heures.',
|
|
30
|
+
'**Le journal des livraisons.** Le code de statut, la durée et le premier kilo-octet de la réponse de chaque tentative, conservés 30 jours.',
|
|
31
|
+
'**Le rejeu.** Tout événement des 30 derniers jours peut être renvoyé, à un point de terminaison ou à tous.',
|
|
32
|
+
]),
|
|
33
|
+
w.h2('Ce qui reste dans votre application'),
|
|
34
|
+
w.p('Votre application décide quels événements existent et ce que contiennent leurs charges utiles. %N% stocke le JSON qu’il reçoit tel quel et livre exactement ces octets : le destinataire voit la même charge que celle envoyée par votre code.'),
|
|
35
|
+
w.h2('Organisation de cette documentation'),
|
|
36
|
+
w.ul([
|
|
37
|
+
'[Premiers pas](/docs/installation) installe l’outil en ligne de commande, lance un serveur local et envoie un premier événement.',
|
|
38
|
+
'[Les guides](/docs/verifier-les-signatures) couvrent la suite du travail : vérifier les signatures, gérer les échecs, configurer et déployer un serveur.',
|
|
39
|
+
'[La référence](/docs/reference-cli) liste chaque commande, chaque clé de configuration et chaque route HTTP.',
|
|
40
|
+
]),
|
|
41
|
+
w.note('Tip', 'Si vous avez dix minutes, allez directement au [démarrage rapide](/docs/demarrage-rapide). Il se termine par un événement signé qui arrive chez un récepteur, sur votre propre machine.'),
|
|
42
|
+
],
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
title: 'Installation',
|
|
46
|
+
slug: 'installation',
|
|
47
|
+
section: 'start',
|
|
48
|
+
summary: 'Installez l’outil en ligne de commande ou lancez l’image de conteneur, puis vérifiez qu’un serveur démarre.',
|
|
49
|
+
body: (w) => [
|
|
50
|
+
w.p('%N% est publié sous la forme d’un seul paquet npm qui contient le serveur et la commande `%cli%`. La même version existe en image de conteneur.'),
|
|
51
|
+
w.h2('Prérequis'),
|
|
52
|
+
w.ref([
|
|
53
|
+
['Node.js', '20.11 ou plus récent', 'Vérifiez avec `node --version`.'],
|
|
54
|
+
['SQLite', 'inclus', 'Utilisé par `%cli% dev`. Rien à installer.'],
|
|
55
|
+
['PostgreSQL', '14 ou plus récent', 'Obligatoire pour `%cli% start` en production.'],
|
|
56
|
+
]),
|
|
57
|
+
w.h2('Installer la commande'),
|
|
58
|
+
w.code('Terminal', '$ npm install --global %cli%\n$ %cli% --version\n%cli% 2.4.1'),
|
|
59
|
+
w.p('Pour figer la version d’un projet, ajoutez-la en dépendance de développement et lancez-la avec npx :'),
|
|
60
|
+
w.code('Terminal', '$ npm install --save-dev %cli%@2.4.1\n$ npx %cli% --version\n%cli% 2.4.1'),
|
|
61
|
+
w.note('Note', 'Sous Linux, installez Node.js avec un gestionnaire de versions comme nvm ou fnm plutôt que de lancer npm avec sudo. Un paquet global installé avec sudo laisse des fichiers que la mise à jour suivante ne peut pas remplacer.'),
|
|
62
|
+
w.h2('Lancer l’image de conteneur'),
|
|
63
|
+
w.p('L’image lit sa configuration dans des variables d’environnement : un premier lancement n’a donc besoin d’aucun fichier. Elle écoute sur le port 8787.'),
|
|
64
|
+
w.code('Terminal', '$ docker run --rm -p 8787:8787 \\\n -e %ENV%_API_KEY=rk_dev_local \\\n ghcr.io/example/%cli%:2.4.1\nListening on http://0.0.0.0:8787'),
|
|
65
|
+
w.h2('Vérifier l’installation'),
|
|
66
|
+
w.p('`%cli% doctor` contrôle la version de Node.js, le fichier de configuration, la base de données et la clé d’API, et indique quoi corriger.'),
|
|
67
|
+
w.code('Terminal', '$ %cli% doctor\nNode.js 22.11.0 ok\nConfiguration %cli%.yaml ok\nDatabase sqlite:.%cli%/dev.db ok\n%ENV%_API_KEY missing, run %cli% init to create one'),
|
|
68
|
+
w.h2('Mettre à jour'),
|
|
69
|
+
w.p('Installez la nouvelle version par-dessus l’ancienne, puis lancez `%cli% migrate` avant de redémarrer le serveur. Chaque version liste ses migrations dans les [nouveautés](/docs/nouveautes).'),
|
|
70
|
+
w.note('Warning', 'Une migration signalée comme bloquante dans les notes de version demande d’arrêter d’abord tous les serveurs. Pour toutes les autres, les serveurs de la version précédente continuent de tourner.'),
|
|
71
|
+
],
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
title: 'Démarrage rapide',
|
|
75
|
+
slug: 'demarrage-rapide',
|
|
76
|
+
section: 'start',
|
|
77
|
+
summary: 'Lancez un serveur local, créez un point de terminaison et envoyez-lui un événement signé, en cinq minutes environ.',
|
|
78
|
+
body: (w) => [
|
|
79
|
+
w.p('Il vous faut %N% installé (voir l’[installation](/docs/installation)) et deux fenêtres de terminal. Tout ce qui suit s’exécute sur votre propre machine.'),
|
|
80
|
+
w.h2('Créer un projet'),
|
|
81
|
+
w.p('Dans un répertoire vide, `%cli% init` écrit un fichier de configuration et un fichier `.env` qui contient une nouvelle clé d’API.'),
|
|
82
|
+
w.code('Terminal', '$ mkdir webhooks-facturation && cd webhooks-facturation\n$ %cli% init\nCreated %cli%.yaml\nCreated .env with %ENV%_API_KEY'),
|
|
83
|
+
w.h2('Démarrer le serveur de développement'),
|
|
84
|
+
w.p('`%cli% dev` démarre un serveur sur le port 8787 avec une base SQLite dans `.%cli%/dev.db`, et affiche chaque livraison au moment où elle a lieu. Laissez-le tourner ; `Ctrl+C` l’arrête.'),
|
|
85
|
+
w.code('Terminal', '$ %cli% dev\nDatabase sqlite:.%cli%/dev.db, 14 migrations applied\nListening on http://localhost:8787'),
|
|
86
|
+
w.h2('Démarrer un récepteur'),
|
|
87
|
+
w.p('Dans le second terminal, `%cli% listen` lance un petit récepteur qui affiche chaque requête reçue, en-têtes compris. Il joue le rôle du point de terminaison que l’un de vos clients exploiterait.'),
|
|
88
|
+
w.code('Terminal', '$ %cli% listen --port 4000\nReceiving on http://localhost:4000/webhooks'),
|
|
89
|
+
w.h2('Créer un point de terminaison'),
|
|
90
|
+
w.p('Un point de terminaison est une URL et les types d’événements auxquels elle s’abonne. Le joker `invoice.*` correspond à tous les types qui commencent par `invoice.`.'),
|
|
91
|
+
w.code('Terminal', '$ %cli% endpoints create \\\n --url http://localhost:4000/webhooks --events "invoice.*"\nEndpoint ep_01J8Z3HQ7N5V created\nSecret whsec_4f9c2a7e1b0d6c3a'),
|
|
92
|
+
w.h2('Envoyer un événement'),
|
|
93
|
+
w.code('Terminal', '$ %cli% events send invoice.paid \\\n --data \'{"invoice_id":"in_1042","amount":4900,"currency":"eur"}\'\nEvent evt_01J8Z3K6QF2M created, 1 delivery scheduled'),
|
|
94
|
+
w.p('Moins d’une seconde plus tard, le récepteur affiche la requête :'),
|
|
95
|
+
w.code('Sortie du récepteur', 'POST /webhooks\nContent-Type: application/json\n%HDR%-Event-Id: evt_01J8Z3K6QF2M\n%HDR%-Event-Type: invoice.paid\n%HDR%-Delivery-Attempt: 1\n%HDR%-Signature: t=1757854800,v1=9b1c4e0f7a2d63b8c5e1f09a4d7b2c6e\n\n{"invoice_id":"in_1042","amount":4900,"currency":"eur"}'),
|
|
96
|
+
w.h2('Pour aller plus loin'),
|
|
97
|
+
w.ul([
|
|
98
|
+
'[Vérifiez la signature](/docs/verifier-les-signatures) dans le code de réception avant de faire confiance à une charge utile.',
|
|
99
|
+
'Lisez les [concepts clés](/docs/concepts-cles) pour voir ce qui se passe quand un point de terminaison échoue.',
|
|
100
|
+
'Envoyez des événements depuis votre application avec l’[API HTTP](/docs/api-http) plutôt qu’en ligne de commande.',
|
|
101
|
+
]),
|
|
102
|
+
],
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
title: 'Concepts clés',
|
|
106
|
+
slug: 'concepts-cles',
|
|
107
|
+
section: 'start',
|
|
108
|
+
summary: 'Événements, points de terminaison, livraisons et tentatives : comment un événement devient plusieurs requêtes signées.',
|
|
109
|
+
body: (w, media) => [
|
|
110
|
+
w.p('Quatre objets décrivent tout ce que fait %N%. Une fois qu’ils sont clairs, la ligne de commande et l’API HTTP se lisent comme deux façons de créer et d’inspecter les mêmes choses.'),
|
|
111
|
+
media.deliveryFlow === undefined
|
|
112
|
+
? []
|
|
113
|
+
: [
|
|
114
|
+
{
|
|
115
|
+
_key: 'concepts-cles-figure',
|
|
116
|
+
_type: 'media',
|
|
117
|
+
id: media.deliveryFlow,
|
|
118
|
+
caption: frenchTypography('Un événement, trois points de terminaison. Chaque livraison est signée et envoyée séparément ; celle qui échoue est réessayée sans retarder les autres.'),
|
|
119
|
+
},
|
|
120
|
+
],
|
|
121
|
+
w.h2('Les événements'),
|
|
122
|
+
w.p('Un événement consigne ce qui s’est passé dans votre application, par exemple le paiement d’une facture. Il a un type (`invoice.paid`), une charge utile JSON de 256 Ko au plus et un identifiant qui commence par `evt_`. Un événement ne change jamais après sa création : pour en corriger un, envoyez-en un nouveau.'),
|
|
123
|
+
w.h2('Les points de terminaison'),
|
|
124
|
+
w.p('Un point de terminaison est une URL qui reçoit des événements, les types d’événements auxquels il s’abonne, et un secret qui commence par `whsec_`. Un type peut se terminer par un joker : `invoice.*` correspond à `invoice.paid` comme à `invoice.voided`.'),
|
|
125
|
+
w.h2('Livraisons et tentatives'),
|
|
126
|
+
w.p('Quand un événement arrive, %N% crée une livraison pour chaque point de terminaison abonné à son type. Chaque livraison est envoyée séparément : un point de terminaison lent ne retarde jamais les autres.'),
|
|
127
|
+
w.p('Une tentative est une requête HTTP pour une livraison. Une réponse 2xx en moins de 10 secondes termine la livraison. Tout autre résultat, redirection comprise, fait échouer la tentative et planifie la suivante.'),
|
|
128
|
+
w.h3('États d’une livraison'),
|
|
129
|
+
w.ref([
|
|
130
|
+
[
|
|
131
|
+
'pending',
|
|
132
|
+
null,
|
|
133
|
+
'En attente de sa première tentative, le plus souvent moins d’une seconde.',
|
|
134
|
+
],
|
|
135
|
+
['retrying', null, 'Au moins une tentative a échoué et une autre est planifiée.'],
|
|
136
|
+
['succeeded', null, 'Une tentative a reçu une réponse 2xx. Aucune autre n’est faite.'],
|
|
137
|
+
[
|
|
138
|
+
'failed',
|
|
139
|
+
null,
|
|
140
|
+
'Les sept tentatives ont échoué. La livraison peut encore être relancée à la main.',
|
|
141
|
+
],
|
|
142
|
+
]),
|
|
143
|
+
w.note('Note', 'Un point de terminaison dont les livraisons échouent depuis cinq jours d’affilée est désactivé. Lancez `%cli% endpoints enable` quand le récepteur refonctionne ; voir [Réessais et rejeu](/docs/reessais-et-rejeu).'),
|
|
144
|
+
],
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
title: 'Vérifier les signatures',
|
|
148
|
+
slug: 'verifier-les-signatures',
|
|
149
|
+
section: 'guides',
|
|
150
|
+
summary: 'Vérifiez qu’une requête vient bien de votre serveur %N% et qu’elle n’a pas été modifiée en route, avant que votre code n’agisse dessus.',
|
|
151
|
+
body: (w) => [
|
|
152
|
+
w.p('Chaque livraison porte un en-tête `%HDR%-Signature`. Le vérifier prouve deux choses : la requête a été signée avec le secret du point de terminaison, et son corps n’a pas changé depuis.'),
|
|
153
|
+
w.h2('Comment la signature est construite'),
|
|
154
|
+
w.p('L’en-tête contient un horodatage Unix et une ou plusieurs signatures :'),
|
|
155
|
+
w.code('', '%HDR%-Signature: t=1757854800,v1=5f2b8c0e61d3a9b7e4f0c2d1a8b6e3f9c7d5a1b2e4f6a8c0d2e4f6a8b0c2d4e6'),
|
|
156
|
+
w.p('Chaque valeur `v1` est le HMAC-SHA256, en hexadécimal, de l’horodatage, d’un point et du corps brut de la requête, avec le secret du point de terminaison pour clé. Pendant la rotation d’un secret, l’en-tête porte une valeur `v1` par secret.'),
|
|
157
|
+
w.h2('Vérifier en Node.js'),
|
|
158
|
+
w.code('verify-signature.ts', `import { createHmac, timingSafeEqual } from 'node:crypto'
|
|
159
|
+
|
|
160
|
+
const TOLERANCE_SECONDS = 300
|
|
161
|
+
|
|
162
|
+
export function verifySignature(
|
|
163
|
+
rawBody: string,
|
|
164
|
+
header: string,
|
|
165
|
+
secret: string,
|
|
166
|
+
): boolean {
|
|
167
|
+
const parts = header.split(',').map((part) => part.split('='))
|
|
168
|
+
const timestamp = Number(parts.find(([key]) => key === 't')?.[1])
|
|
169
|
+
// Refuse les signatures trop anciennes : une requête interceptée
|
|
170
|
+
// ne pourra pas être rejouée plus tard
|
|
171
|
+
const age = Math.abs(Date.now() / 1000 - timestamp)
|
|
172
|
+
if (Number.isNaN(timestamp) || age > TOLERANCE_SECONDS) return false
|
|
173
|
+
|
|
174
|
+
const expected = createHmac('sha256', secret)
|
|
175
|
+
.update(timestamp + '.' + rawBody)
|
|
176
|
+
.digest()
|
|
177
|
+
return parts
|
|
178
|
+
.filter(([key]) => key === 'v1')
|
|
179
|
+
.some(([, value]) => {
|
|
180
|
+
const received = Buffer.from(value ?? '', 'hex')
|
|
181
|
+
return (
|
|
182
|
+
received.length === expected.length &&
|
|
183
|
+
timingSafeEqual(received, expected)
|
|
184
|
+
)
|
|
185
|
+
})
|
|
186
|
+
}`),
|
|
187
|
+
w.h3('Dans une route Express'),
|
|
188
|
+
w.p('Lisez le corps en octets bruts sur la seule route du webhook, afin que la signature soit calculée sur exactement ce qui a été envoyé.'),
|
|
189
|
+
w.code('server.ts', `import express from 'express'
|
|
190
|
+
import { handleEvent } from './events.js'
|
|
191
|
+
import { verifySignature } from './verify-signature.js'
|
|
192
|
+
|
|
193
|
+
const app = express()
|
|
194
|
+
const rawJson = express.raw({ type: 'application/json' })
|
|
195
|
+
|
|
196
|
+
app.post('/webhooks', rawJson, (req, res) => {
|
|
197
|
+
const body = req.body.toString('utf8')
|
|
198
|
+
const header = req.get('%HDR%-Signature') ?? ''
|
|
199
|
+
const secret = process.env.WEBHOOK_SECRET ?? ''
|
|
200
|
+
if (verifySignature(body, header, secret) === false) {
|
|
201
|
+
res.status(400).send('invalid signature')
|
|
202
|
+
return
|
|
203
|
+
}
|
|
204
|
+
// Répondre d’abord, traiter ensuite : une tentative expire après 10 s
|
|
205
|
+
res.status(204).end()
|
|
206
|
+
handleEvent(JSON.parse(body))
|
|
207
|
+
})`),
|
|
208
|
+
w.note('Warning', 'La plupart des frameworks analysent le JSON avant que votre gestionnaire ne s’exécute. Sérialiser à nouveau l’objet obtenu change les espaces et l’ordre des clés, et la signature ne correspond plus. Vérifiez toujours le corps brut.'),
|
|
209
|
+
w.h2('Vérifier en Python'),
|
|
210
|
+
w.code('verify_signature.py', `import hashlib
|
|
211
|
+
import hmac
|
|
212
|
+
import time
|
|
213
|
+
|
|
214
|
+
TOLERANCE = 300
|
|
215
|
+
|
|
216
|
+
def verify_signature(raw_body: bytes, header: str, secret: str) -> bool:
|
|
217
|
+
parts = [part.split("=", 1) for part in header.split(",")]
|
|
218
|
+
stamps = [value for name, value in parts if name == "t"]
|
|
219
|
+
if not stamps or abs(time.time() - int(stamps[0])) > TOLERANCE:
|
|
220
|
+
return False
|
|
221
|
+
signed = stamps[0].encode() + b"." + raw_body
|
|
222
|
+
key = secret.encode()
|
|
223
|
+
digest = hmac.new(key, signed, hashlib.sha256).hexdigest()
|
|
224
|
+
received = [value for name, value in parts if name == "v1"]
|
|
225
|
+
return any(hmac.compare_digest(digest, value) for value in received)`),
|
|
226
|
+
w.h2('Renouveler un secret'),
|
|
227
|
+
w.p('Un renouvellement émet un nouveau secret et garde le précédent valide pendant un délai de grâce : les destinataires peuvent basculer sans rejeter une seule requête.'),
|
|
228
|
+
w.code('Terminal', '$ %cli% endpoints rotate-secret ep_01J8Z3HQ7N5V --grace 24h\nNew secret whsec_7d31b0c9e25a4f18\nPrevious secret valid until 2026-09-15 09:12 UTC'),
|
|
229
|
+
],
|
|
230
|
+
},
|
|
231
|
+
{
|
|
232
|
+
title: 'Réessais et rejeu',
|
|
233
|
+
slug: 'reessais-et-rejeu',
|
|
234
|
+
section: 'guides',
|
|
235
|
+
summary: 'Ce qui se passe quand un point de terminaison échoue, comment relancer une livraison à la main et comment renvoyer des événements passés.',
|
|
236
|
+
body: (w) => [
|
|
237
|
+
w.p('Les points de terminaison tombent, les déploiements renvoient des erreurs et les certificats expirent. Une tentative échouée ouvre un calendrier, et le journal des livraisons en montre chaque étape.'),
|
|
238
|
+
w.h2('Le calendrier des réessais'),
|
|
239
|
+
w.p('Une livraison reçoit jusqu’à sept tentatives. Après un échec, la tentative suivante attend l’intervalle ci-dessous, plus jusqu’à 10 % de gigue aléatoire : un point de terminaison qui se remet d’une panne ne reçoit pas tous les réessais au même instant.'),
|
|
240
|
+
w.ref([
|
|
241
|
+
['30s', 'tentative 2', 'Couvre un redémarrage ou un déploiement en cours.'],
|
|
242
|
+
['2m', 'tentative 3', 'Couvre une courte coupure réseau.'],
|
|
243
|
+
['10m', 'tentative 4', 'Couvre un déploiement raté que l’on annule.'],
|
|
244
|
+
['1h', 'tentative 5', 'Couvre la plupart des incidents côté récepteur.'],
|
|
245
|
+
['6h', 'tentative 6', 'Couvre une panne qui dure jusque dans la soirée.'],
|
|
246
|
+
['24h', 'tentative 7', 'La dernière, un peu plus de 31 heures après la première.'],
|
|
247
|
+
]),
|
|
248
|
+
w.p('Modifiez les intervalles avec `delivery.retries.schedule` dans le [fichier de configuration](/docs/reference-configuration). Il y a toujours une tentative de plus que d’intervalles.'),
|
|
249
|
+
w.h2('Relancer une livraison à la main'),
|
|
250
|
+
w.p('Une fois le récepteur réparé, inutile d’attendre la prochaine tentative planifiée. Listez les livraisons en échec du point de terminaison et relancez celles qu’il vous faut :'),
|
|
251
|
+
w.code('Terminal', '$ %cli% deliveries list --endpoint ep_01J8Z3HQ7N5V --status failed\nID EVENT ATTEMPTS LAST RESULT\ndlv_01J8Z9AQ2C7R invoice.paid 7 503\ndlv_01J8Z9B4TMW1 invoice.voided 7 timeout\n$ %cli% deliveries retry dlv_01J8Z9AQ2C7R\nAttempt 8 scheduled for dlv_01J8Z9AQ2C7R'),
|
|
252
|
+
w.p('Une relance manuelle ajoute une tentative à la même livraison. Le récepteur voit le même `%HDR%-Event-Id` qu’avant et peut reconnaître une requête déjà traitée.'),
|
|
253
|
+
w.h2('Rejouer des événements passés'),
|
|
254
|
+
w.p('Un rejeu crée de nouvelles livraisons pour des événements qui existent déjà. Utilisez-le après avoir ajouté un point de terminaison qui aurait dû recevoir des événements antérieurs, ou après qu’un récepteur a perdu des données.'),
|
|
255
|
+
w.code('Terminal', '$ %cli% events replay --since 2026-09-12T08:00:00Z \\\n --types "invoice.*" --endpoint ep_01J8Z3HQ7N5V\n38 events matched, 38 deliveries scheduled'),
|
|
256
|
+
w.note('Note', 'Un rejeu n’atteint que les événements encore dans la fenêtre de conservation, 30 jours par défaut. Un événement rejoué garde son identifiant d’origine : les récepteurs doivent s’appuyer sur `%HDR%-Event-Id` pour ignorer ceux qu’ils ont déjà traités.'),
|
|
257
|
+
],
|
|
258
|
+
},
|
|
259
|
+
{
|
|
260
|
+
title: 'Configuration',
|
|
261
|
+
slug: 'configuration',
|
|
262
|
+
section: 'guides',
|
|
263
|
+
summary: 'Où le serveur lit ses réglages, et lesquels revoir avant de passer un serveur en production.',
|
|
264
|
+
body: (w) => [
|
|
265
|
+
w.p('Le serveur lit `%cli%.yaml` dans son répertoire de travail, puis applique par-dessus les variables d’environnement. Une valeur définie dans l’environnement l’emporte toujours : un seul fichier sert au développement comme à la production.'),
|
|
266
|
+
w.code('%cli%.yaml', `server:
|
|
267
|
+
port: 8787
|
|
268
|
+
public_url: http://localhost:8787
|
|
269
|
+
|
|
270
|
+
database:
|
|
271
|
+
url: sqlite:.%cli%/dev.db
|
|
272
|
+
|
|
273
|
+
delivery:
|
|
274
|
+
timeout: 10s
|
|
275
|
+
concurrency: 32
|
|
276
|
+
retries:
|
|
277
|
+
schedule: [30s, 2m, 10m, 1h, 6h, 24h]
|
|
278
|
+
|
|
279
|
+
retention:
|
|
280
|
+
events: 30d`),
|
|
281
|
+
w.h2('Variables d’environnement'),
|
|
282
|
+
w.p('Chaque clé a sa variable d’environnement : écrivez son chemin en majuscules, remplacez les points par des tirets bas et ajoutez le préfixe `%ENV%_`. `database.url` devient `%ENV%_DATABASE_URL`.'),
|
|
283
|
+
w.ref([
|
|
284
|
+
[
|
|
285
|
+
'%ENV%_API_KEY',
|
|
286
|
+
'obligatoire',
|
|
287
|
+
'Authentifie les appels à l’API HTTP, y compris ceux de la ligne de commande.',
|
|
288
|
+
],
|
|
289
|
+
[
|
|
290
|
+
'%ENV%_DATABASE_URL',
|
|
291
|
+
'facultative',
|
|
292
|
+
'Remplace `database.url`. À utiliser pour les identifiants de production.',
|
|
293
|
+
],
|
|
294
|
+
[
|
|
295
|
+
'%ENV%_CONFIG',
|
|
296
|
+
'facultative',
|
|
297
|
+
'Chemin d’un fichier de configuration autre que `%cli%.yaml`.',
|
|
298
|
+
],
|
|
299
|
+
]),
|
|
300
|
+
w.note('Warning', 'Gardez les secrets hors de `%cli%.yaml`. Ce fichier est fait pour être versionné avec votre code ; la clé d’API et le mot de passe de la base relèvent de l’environnement ou du gestionnaire de secrets de votre plateforme.'),
|
|
301
|
+
w.h2('Avant de passer un serveur en production'),
|
|
302
|
+
w.ul([
|
|
303
|
+
'`server.public_url`, pour que les liens du journal des livraisons pointent vers le bon hôte.',
|
|
304
|
+
'`delivery.concurrency`, le nombre de requêtes qu’un serveur envoie en même temps. Augmentez-le quand des livraisons commencent à attendre.',
|
|
305
|
+
'`retention.events`, si vos clients attendent des rejeux qui remontent au-delà de 30 jours.',
|
|
306
|
+
]),
|
|
307
|
+
w.p('La [référence de configuration](/docs/reference-configuration) liste chaque clé avec son type et sa valeur par défaut.'),
|
|
308
|
+
],
|
|
309
|
+
},
|
|
310
|
+
{
|
|
311
|
+
title: 'Déploiement en production',
|
|
312
|
+
slug: 'deploiement-en-production',
|
|
313
|
+
section: 'guides',
|
|
314
|
+
summary: 'Faites tourner %N% sur PostgreSQL derrière un reverse proxy, avec des sondes de santé et plusieurs serveurs.',
|
|
315
|
+
body: (w) => [
|
|
316
|
+
w.p('Un serveur de production a besoin de trois choses : une base PostgreSQL, une clé d’API dans l’environnement, et un superviseur qui relance le processus s’il s’arrête.'),
|
|
317
|
+
w.h2('Préparer la base de données'),
|
|
318
|
+
w.code('Terminal', '# Le mot de passe vient de PGPASSWORD ou du gestionnaire de secrets\n$ export %ENV%_DATABASE_URL=postgres://%cli%@db.internal:5432/%cli%\n$ %cli% migrate\nApplied 14 migrations in 1.8 s'),
|
|
319
|
+
w.p('Lancez `%cli% migrate` une fois par version, avant le démarrage de la nouvelle. Un serveur refuse de démarrer sur une base qui a des migrations en attente.'),
|
|
320
|
+
w.h2('Lancer le serveur'),
|
|
321
|
+
w.p('L’image exécute `%cli% start` par défaut. Avec Docker Compose :'),
|
|
322
|
+
w.code('compose.yaml', `services:
|
|
323
|
+
%cli%:
|
|
324
|
+
image: ghcr.io/example/%cli%:2.4.1
|
|
325
|
+
ports:
|
|
326
|
+
- "8787:8787"
|
|
327
|
+
environment:
|
|
328
|
+
%ENV%_API_KEY: \${%ENV%_API_KEY}
|
|
329
|
+
%ENV%_DATABASE_URL: \${%ENV%_DATABASE_URL}
|
|
330
|
+
healthcheck:
|
|
331
|
+
test: ["CMD", "wget", "-qO-", "http://localhost:8787/healthz"]
|
|
332
|
+
interval: 10s
|
|
333
|
+
restart: unless-stopped`),
|
|
334
|
+
w.h2('Placer un proxy devant'),
|
|
335
|
+
w.p('Terminez TLS sur un reverse proxy ou un répartiteur de charge et transmettez le trafic au port 8787. Seule l’API HTTP reçoit du trafic entrant ; les livraisons sont des requêtes sortantes.'),
|
|
336
|
+
w.h2('Sondes de santé'),
|
|
337
|
+
w.p('`GET /healthz` répond 200 quand la base est joignable et ses migrations à jour, et 503 sinon. Pointez-y à la fois les sondes de vivacité et de disponibilité.'),
|
|
338
|
+
w.h2('Faire tourner plusieurs serveurs'),
|
|
339
|
+
w.p('Plusieurs serveurs peuvent partager une base. Chacun réserve ses livraisons avec un verrou de ligne (`FOR UPDATE SKIP LOCKED`) : deux serveurs n’envoient jamais la même tentative. Ajoutez un serveur quand `%cli% status` montre des livraisons qui attendent plus de quelques secondes.'),
|
|
340
|
+
w.note('Tip', 'Arrêtez un serveur avec SIGTERM. Il termine les tentatives en cours, pendant la durée du délai de livraison au plus, avant de s’arrêter.'),
|
|
341
|
+
],
|
|
342
|
+
},
|
|
343
|
+
{
|
|
344
|
+
title: 'Référence de la CLI',
|
|
345
|
+
slug: 'reference-cli',
|
|
346
|
+
section: 'reference',
|
|
347
|
+
summary: 'Chaque commande et chaque option globale de la ligne de commande %N%, et ce que signifie chaque code de sortie.',
|
|
348
|
+
body: (w) => [
|
|
349
|
+
w.p('Chaque commande lit `%cli%.yaml` et les variables d’environnement `%ENV%_` décrites dans la page [Configuration](/docs/configuration). `%cli% help COMMANDE` affiche les options d’une commande.'),
|
|
350
|
+
w.h2('Projet et serveur'),
|
|
351
|
+
w.ref([
|
|
352
|
+
[
|
|
353
|
+
'%cli% init',
|
|
354
|
+
null,
|
|
355
|
+
'Écrit `%cli%.yaml` et un fichier `.env` avec une nouvelle clé d’API dans le répertoire courant.',
|
|
356
|
+
],
|
|
357
|
+
[
|
|
358
|
+
'%cli% dev',
|
|
359
|
+
null,
|
|
360
|
+
'Démarre un serveur de développement sur SQLite et affiche chaque livraison.',
|
|
361
|
+
],
|
|
362
|
+
[
|
|
363
|
+
'%cli% start',
|
|
364
|
+
null,
|
|
365
|
+
'Démarre un serveur de production. `database.url` doit pointer vers PostgreSQL.',
|
|
366
|
+
],
|
|
367
|
+
['%cli% migrate', null, 'Applique les migrations de base en attente, puis s’arrête.'],
|
|
368
|
+
[
|
|
369
|
+
'%cli% status',
|
|
370
|
+
null,
|
|
371
|
+
'Montre les serveurs en marche, les livraisons en file et la plus ancienne en attente.',
|
|
372
|
+
],
|
|
373
|
+
[
|
|
374
|
+
'%cli% doctor',
|
|
375
|
+
null,
|
|
376
|
+
'Contrôle Node.js, la configuration, la base de données et la clé d’API.',
|
|
377
|
+
],
|
|
378
|
+
]),
|
|
379
|
+
w.h2('Points de terminaison'),
|
|
380
|
+
w.ref([
|
|
381
|
+
[
|
|
382
|
+
'%cli% endpoints create',
|
|
383
|
+
null,
|
|
384
|
+
'Crée un point de terminaison à partir de `--url` et `--events`, et affiche son secret une seule fois.',
|
|
385
|
+
],
|
|
386
|
+
[
|
|
387
|
+
'%cli% endpoints list',
|
|
388
|
+
null,
|
|
389
|
+
'Liste les points de terminaison avec leur état et leur taux d’échec sur 24 heures.',
|
|
390
|
+
],
|
|
391
|
+
[
|
|
392
|
+
'%cli% endpoints disable ID',
|
|
393
|
+
null,
|
|
394
|
+
'Arrête les livraisons vers un point de terminaison. Les nouvelles attendent sa réactivation.',
|
|
395
|
+
],
|
|
396
|
+
['%cli% endpoints enable ID', null, 'Reprend les livraisons vers un point de terminaison.'],
|
|
397
|
+
[
|
|
398
|
+
'%cli% endpoints rotate-secret ID',
|
|
399
|
+
null,
|
|
400
|
+
'Émet un nouveau secret. `--grace` garde le précédent valide, 24 heures par défaut.',
|
|
401
|
+
],
|
|
402
|
+
]),
|
|
403
|
+
w.h2('Événements et livraisons'),
|
|
404
|
+
w.ref([
|
|
405
|
+
[
|
|
406
|
+
'%cli% events send TYPE',
|
|
407
|
+
null,
|
|
408
|
+
'Envoie un événement. La charge utile vient de `--data` ou de l’entrée standard.',
|
|
409
|
+
],
|
|
410
|
+
[
|
|
411
|
+
'%cli% events replay',
|
|
412
|
+
null,
|
|
413
|
+
'Crée de nouvelles livraisons pour des événements passés, filtrés par `--since`, `--types` et `--endpoint`.',
|
|
414
|
+
],
|
|
415
|
+
[
|
|
416
|
+
'%cli% deliveries list',
|
|
417
|
+
null,
|
|
418
|
+
'Liste les livraisons, filtrées par `--endpoint` et `--status`.',
|
|
419
|
+
],
|
|
420
|
+
[
|
|
421
|
+
'%cli% deliveries retry ID',
|
|
422
|
+
null,
|
|
423
|
+
'Planifie sans attendre une tentative supplémentaire pour une livraison.',
|
|
424
|
+
],
|
|
425
|
+
[
|
|
426
|
+
'%cli% listen',
|
|
427
|
+
null,
|
|
428
|
+
'Démarre un récepteur local qui affiche chaque requête. Pour le développement uniquement.',
|
|
429
|
+
],
|
|
430
|
+
]),
|
|
431
|
+
w.h2('Options globales'),
|
|
432
|
+
w.ref([
|
|
433
|
+
['--config PATH', 'chaîne', 'Lit un autre fichier de configuration.'],
|
|
434
|
+
[
|
|
435
|
+
'--api-url URL',
|
|
436
|
+
'chaîne, http://localhost:8787 par défaut',
|
|
437
|
+
'Le serveur avec lequel une commande dialogue.',
|
|
438
|
+
],
|
|
439
|
+
['--json', 'booléen', 'Affiche un objet JSON par ligne au lieu d’un tableau.'],
|
|
440
|
+
['--quiet', 'booléen', 'N’affiche que les erreurs.'],
|
|
441
|
+
]),
|
|
442
|
+
w.h2('Codes de sortie'),
|
|
443
|
+
w.ref([
|
|
444
|
+
['0', null, 'La commande a réussi.'],
|
|
445
|
+
['1', null, 'La commande a échoué. La raison est écrite sur la sortie d’erreur standard.'],
|
|
446
|
+
['2', null, 'Un argument ou la configuration est invalide.'],
|
|
447
|
+
['3', null, 'Le serveur est injoignable.'],
|
|
448
|
+
]),
|
|
449
|
+
],
|
|
450
|
+
},
|
|
451
|
+
{
|
|
452
|
+
title: 'Référence de configuration',
|
|
453
|
+
slug: 'reference-configuration',
|
|
454
|
+
section: 'reference',
|
|
455
|
+
summary: 'Chaque clé acceptée par le fichier de configuration, avec son type et sa valeur par défaut.',
|
|
456
|
+
body: (w) => [
|
|
457
|
+
w.p('Les durées acceptent les suffixes `ms`, `s`, `m`, `h` et `d`. Chaque clé peut aussi être définie par une variable d’environnement, comme l’explique la page [Configuration](/docs/configuration).'),
|
|
458
|
+
w.h2('server'),
|
|
459
|
+
w.ref([
|
|
460
|
+
['server.port', 'entier, 8787 par défaut', 'Port sur lequel écoute l’API HTTP.'],
|
|
461
|
+
['server.host', 'chaîne, 0.0.0.0 par défaut', 'Interface réseau à laquelle se lier.'],
|
|
462
|
+
[
|
|
463
|
+
'server.public_url',
|
|
464
|
+
'chaîne',
|
|
465
|
+
'Adresse utilisée dans les liens du journal des livraisons.',
|
|
466
|
+
],
|
|
467
|
+
]),
|
|
468
|
+
w.h2('database'),
|
|
469
|
+
w.ref([
|
|
470
|
+
[
|
|
471
|
+
'database.url',
|
|
472
|
+
'chaîne, obligatoire',
|
|
473
|
+
'Un chemin `sqlite:` ou une chaîne de connexion `postgres://`.',
|
|
474
|
+
],
|
|
475
|
+
['database.pool_size', 'entier, 10 par défaut', 'Connexions qu’un serveur garde ouvertes.'],
|
|
476
|
+
]),
|
|
477
|
+
w.h2('delivery'),
|
|
478
|
+
w.ref([
|
|
479
|
+
[
|
|
480
|
+
'delivery.timeout',
|
|
481
|
+
'durée, 10s par défaut',
|
|
482
|
+
'Temps dont dispose un point de terminaison pour répondre à une tentative.',
|
|
483
|
+
],
|
|
484
|
+
[
|
|
485
|
+
'delivery.concurrency',
|
|
486
|
+
'entier, 32 par défaut',
|
|
487
|
+
'Requêtes qu’un serveur envoie en même temps.',
|
|
488
|
+
],
|
|
489
|
+
[
|
|
490
|
+
'delivery.retries.schedule',
|
|
491
|
+
'liste de durées',
|
|
492
|
+
'Attentes entre les tentatives. La valeur par défaut est `[30s, 2m, 10m, 1h, 6h, 24h]`.',
|
|
493
|
+
],
|
|
494
|
+
[
|
|
495
|
+
'delivery.retries.jitter',
|
|
496
|
+
'nombre, 0.1 par défaut',
|
|
497
|
+
'Part de chaque attente ajoutée au hasard.',
|
|
498
|
+
],
|
|
499
|
+
[
|
|
500
|
+
'delivery.disable_after',
|
|
501
|
+
'durée, 5d par défaut',
|
|
502
|
+
'Désactive un point de terminaison en échec depuis cette durée.',
|
|
503
|
+
],
|
|
504
|
+
]),
|
|
505
|
+
w.h2('signing'),
|
|
506
|
+
w.ref([
|
|
507
|
+
[
|
|
508
|
+
'signing.tolerance',
|
|
509
|
+
'durée, 5m par défaut',
|
|
510
|
+
'Âge à partir duquel les bibliothèques officielles rejettent une signature.',
|
|
511
|
+
],
|
|
512
|
+
['signing.header', 'chaîne, %HDR%-Signature par défaut', 'Nom de l’en-tête de signature.'],
|
|
513
|
+
]),
|
|
514
|
+
w.h2('retention'),
|
|
515
|
+
w.ref([
|
|
516
|
+
[
|
|
517
|
+
'retention.events',
|
|
518
|
+
'durée, 30d par défaut',
|
|
519
|
+
'Durée de conservation des événements, des livraisons et des tentatives.',
|
|
520
|
+
],
|
|
521
|
+
[
|
|
522
|
+
'retention.response_body',
|
|
523
|
+
'taille, 1KB par défaut',
|
|
524
|
+
'Part du corps de chaque réponse qui est conservée.',
|
|
525
|
+
],
|
|
526
|
+
]),
|
|
527
|
+
],
|
|
528
|
+
},
|
|
529
|
+
{
|
|
530
|
+
title: 'API HTTP',
|
|
531
|
+
slug: 'api-http',
|
|
532
|
+
section: 'reference',
|
|
533
|
+
summary: 'Envoyez des événements et inspectez les livraisons en HTTP, avec des exemples de requêtes, de réponses et d’erreurs.',
|
|
534
|
+
body: (w) => [
|
|
535
|
+
w.p('Chaque serveur %N% expose l’API sous `/v1`. Les requêtes et les réponses sont en JSON, et toute requête, sauf la sonde de santé, s’authentifie avec la clé d’API comme jeton porteur.'),
|
|
536
|
+
w.code('Terminal', '$ curl http://localhost:8787/v1/endpoints \\\n -H "Authorization: Bearer $%ENV%_API_KEY"'),
|
|
537
|
+
w.h2('Ressources'),
|
|
538
|
+
w.ref([
|
|
539
|
+
['POST /v1/events', null, 'Envoie un événement et planifie ses livraisons.'],
|
|
540
|
+
[
|
|
541
|
+
'GET /v1/events/{id}',
|
|
542
|
+
null,
|
|
543
|
+
'Renvoie un événement et l’état de chacune de ses livraisons.',
|
|
544
|
+
],
|
|
545
|
+
['POST /v1/endpoints', null, 'Crée un point de terminaison.'],
|
|
546
|
+
['GET /v1/endpoints', null, 'Liste les points de terminaison.'],
|
|
547
|
+
[
|
|
548
|
+
'PATCH /v1/endpoints/{id}',
|
|
549
|
+
null,
|
|
550
|
+
'Modifie l’URL, les types d’événements ou l’état d’un point de terminaison.',
|
|
551
|
+
],
|
|
552
|
+
[
|
|
553
|
+
'GET /v1/deliveries',
|
|
554
|
+
null,
|
|
555
|
+
'Liste les livraisons, filtrées par `status`, `endpoint` ou `event`.',
|
|
556
|
+
],
|
|
557
|
+
[
|
|
558
|
+
'GET /v1/deliveries/{id}/attempts',
|
|
559
|
+
null,
|
|
560
|
+
'Liste les tentatives d’une livraison, de la plus récente à la plus ancienne.',
|
|
561
|
+
],
|
|
562
|
+
['POST /v1/deliveries/{id}/retry', null, 'Planifie une tentative supplémentaire.'],
|
|
563
|
+
['GET /healthz', null, 'Indique si le serveur est prêt. Ne demande aucune clé d’API.'],
|
|
564
|
+
]),
|
|
565
|
+
w.h2('Envoyer un événement'),
|
|
566
|
+
w.p('`idempotency_key` est facultative. Une seconde requête avec la même clé dans les 24 heures renvoie le premier événement au lieu d’en créer un nouveau.'),
|
|
567
|
+
w.code('Requête', `POST /v1/events HTTP/1.1
|
|
568
|
+
Host: localhost:8787
|
|
569
|
+
Authorization: Bearer rk_live_2Jx8Qd4mT7
|
|
570
|
+
Content-Type: application/json
|
|
571
|
+
|
|
572
|
+
{
|
|
573
|
+
"type": "invoice.paid",
|
|
574
|
+
"idempotency_key": "invoice-in_1042-paid",
|
|
575
|
+
"data": {
|
|
576
|
+
"invoice_id": "in_1042",
|
|
577
|
+
"amount": 4900,
|
|
578
|
+
"currency": "eur"
|
|
579
|
+
}
|
|
580
|
+
}`),
|
|
581
|
+
w.code('Réponse : 202 Accepted', `{
|
|
582
|
+
"id": "evt_01J8Z3K6QF2M",
|
|
583
|
+
"type": "invoice.paid",
|
|
584
|
+
"created_at": "2026-09-14T09:12:04Z",
|
|
585
|
+
"deliveries": 3
|
|
586
|
+
}`),
|
|
587
|
+
w.h2('Lister les livraisons en échec'),
|
|
588
|
+
w.code('Requête', 'GET /v1/deliveries?status=failed&limit=2 HTTP/1.1\nHost: localhost:8787\nAuthorization: Bearer rk_live_2Jx8Qd4mT7'),
|
|
589
|
+
w.code('Réponse : 200 OK', `{
|
|
590
|
+
"data": [
|
|
591
|
+
{
|
|
592
|
+
"id": "dlv_01J8Z9AQ2C7R",
|
|
593
|
+
"event": "evt_01J8Z3K6QF2M",
|
|
594
|
+
"endpoint": "ep_01J8Z3HQ7N5V",
|
|
595
|
+
"status": "failed",
|
|
596
|
+
"attempts": 7,
|
|
597
|
+
"last_response": { "status": 503, "duration_ms": 10000 }
|
|
598
|
+
}
|
|
599
|
+
],
|
|
600
|
+
"next_cursor": null
|
|
601
|
+
}`),
|
|
602
|
+
w.h2('Erreurs'),
|
|
603
|
+
w.p('Une erreur porte un code de statut standard et un corps avec un `code` stable et un `message` lisible.'),
|
|
604
|
+
w.code('Réponse : 409 Conflict', `{
|
|
605
|
+
"error": {
|
|
606
|
+
"code": "idempotency_conflict",
|
|
607
|
+
"message": "This idempotency key was used with a different body."
|
|
608
|
+
}
|
|
609
|
+
}`),
|
|
610
|
+
w.ref([
|
|
611
|
+
[
|
|
612
|
+
'400 invalid_request',
|
|
613
|
+
null,
|
|
614
|
+
'Le corps n’est pas du JSON valide, ou un champ obligatoire manque.',
|
|
615
|
+
],
|
|
616
|
+
['401 unauthorized', null, 'La clé d’API est absente ou a été révoquée.'],
|
|
617
|
+
['404 not_found', null, 'Aucun objet n’existe avec cet identifiant.'],
|
|
618
|
+
[
|
|
619
|
+
'409 idempotency_conflict',
|
|
620
|
+
null,
|
|
621
|
+
'La clé d’idempotence a été utilisée avec un autre corps au cours des 24 dernières heures.',
|
|
622
|
+
],
|
|
623
|
+
['413 payload_too_large', null, 'La charge utile de l’événement dépasse 256 Ko.'],
|
|
624
|
+
[
|
|
625
|
+
'429 rate_limited',
|
|
626
|
+
null,
|
|
627
|
+
'Trop de requêtes. Attendez le nombre de secondes indiqué dans l’en-tête `Retry-After`.',
|
|
628
|
+
],
|
|
629
|
+
]),
|
|
630
|
+
],
|
|
631
|
+
},
|
|
632
|
+
{
|
|
633
|
+
title: 'Dépannage',
|
|
634
|
+
slug: 'depannage',
|
|
635
|
+
section: 'help',
|
|
636
|
+
summary: 'Les problèmes les plus fréquents, la cause de chacun et la façon de les résoudre.',
|
|
637
|
+
body: (w) => [
|
|
638
|
+
w.p('Commencez par `%cli% doctor`, qui repère la plupart des problèmes de configuration. Les sections ci-dessous couvrent ce qu’il ne peut pas voir depuis le serveur.'),
|
|
639
|
+
w.h2('Les signatures ne correspondent jamais'),
|
|
640
|
+
w.p('Presque toujours, le récepteur vérifie un corps qui a été analysé puis sérialisé de nouveau. Vérifiez les octets bruts, comme le montre la page [Vérifier les signatures](/docs/verifier-les-signatures). Si le corps est brut et que les signatures échouent encore, vérifiez que le secret appartient bien à ce point de terminaison : chacun a le sien.'),
|
|
641
|
+
w.h2('Horodatage hors de la tolérance'),
|
|
642
|
+
w.p('L’horloge du récepteur est décalée de plus de cinq minutes par rapport à celle du serveur. Activez NTP sur cet hôte, ou augmentez `signing.tolerance` le temps de corriger.'),
|
|
643
|
+
w.h2('Des livraisons restent en attente'),
|
|
644
|
+
w.p('Aucun serveur ne prend les livraisons en charge. `%cli% migrate` s’arrête une fois terminé : vérifiez donc qu’un processus exécute `%cli% start`, puis regardez `%cli% status` :'),
|
|
645
|
+
w.code('Terminal', '$ %cli% status\nServers 2 running\nQueued 1,284 deliveries\nOldest waiting 3 min 12 s\nAttempts 2,410 per minute'),
|
|
646
|
+
w.p('Une file qui grossit alors que des serveurs tournent signifie que `delivery.concurrency` est trop bas pour votre trafic, ou qu’un point de terminaison est assez lent pour occuper tous les emplacements de requête. Augmentez la concurrence ou ajoutez un serveur.'),
|
|
647
|
+
w.h2('Chaque tentative échoue sur une redirection'),
|
|
648
|
+
w.p('%N% ne suit pas les redirections : une requête signée transmise à un autre hôte lui livrerait sa charge utile, alors que personne ne l’a choisi. Mettez le point de terminaison à jour avec son URL définitive, le plus souvent la forme en `https://` de l’adresse.'),
|
|
649
|
+
w.h2('Connexion refusée depuis un conteneur'),
|
|
650
|
+
w.p('Dans un conteneur, `localhost` désigne le conteneur lui-même. Utilisez `host.docker.internal` avec Docker Desktop, ou le nom du service sur un réseau Compose.'),
|
|
651
|
+
w.h2('Un point de terminaison a été désactivé'),
|
|
652
|
+
w.p('Un point de terminaison est désactivé après cinq jours de livraisons en échec. Réparez le récepteur, lancez `%cli% endpoints enable ID`, puis renvoyez ce qu’il a manqué avec `%cli% events replay --endpoint ID --since` suivi de l’heure à laquelle les échecs ont commencé.'),
|
|
653
|
+
],
|
|
654
|
+
},
|
|
655
|
+
{
|
|
656
|
+
title: 'Nouveautés',
|
|
657
|
+
slug: 'nouveautes',
|
|
658
|
+
section: 'help',
|
|
659
|
+
summary: 'Les changements de chaque version de %N%, de la plus récente à la plus ancienne, avec la marche à suivre pour mettre à jour.',
|
|
660
|
+
body: (w) => [
|
|
661
|
+
w.p('%N% suit le versionnage sémantique. Une version mineure ne change jamais le format de configuration ni l’API HTTP d’une manière qui casserait une intégration existante.'),
|
|
662
|
+
w.h2('2.4, 2 septembre 2026'),
|
|
663
|
+
w.ul([
|
|
664
|
+
'Les types d’événements d’un point de terminaison acceptent un joker final, comme `invoice.*`.',
|
|
665
|
+
'`%cli% events replay` accepte `--since` et `--types` : un rejeu n’a plus besoin d’une liste d’identifiants d’événements.',
|
|
666
|
+
'L’en-tête `%HDR%-Delivery-Attempt` indique au récepteur de quelle tentative il s’agit.',
|
|
667
|
+
'`delivery.retries.jitter` étale les réessais après une panne. La valeur par défaut est de 10 %.',
|
|
668
|
+
]),
|
|
669
|
+
w.h3('Mettre à jour depuis la 2.3'),
|
|
670
|
+
w.p('Lancez `%cli% migrate` avant de démarrer la 2.4. La migration ajoute un index à la table des livraisons et prend moins d’une minute sur dix millions de lignes.'),
|
|
671
|
+
w.h2('2.3, 15 juillet 2026'),
|
|
672
|
+
w.ul([
|
|
673
|
+
'`%cli% dev` s’exécute sur SQLite : un serveur local n’a plus besoin de PostgreSQL.',
|
|
674
|
+
'Nouvelle commande `%cli% doctor`.',
|
|
675
|
+
'`retention.response_body` règle la part de chaque réponse qui est conservée.',
|
|
676
|
+
]),
|
|
677
|
+
w.h3('Mettre à jour depuis la 2.2'),
|
|
678
|
+
w.p('Rien à changer dans le fichier de configuration. Les projets de développement qui pointaient `database.url` vers un PostgreSQL local peuvent passer à SQLite en retirant la clé, puisque `%cli% dev` crée désormais sa propre base.'),
|
|
679
|
+
w.h2('2.2, 20 mai 2026'),
|
|
680
|
+
w.ul([
|
|
681
|
+
'Clés d’idempotence sur `POST /v1/events`.',
|
|
682
|
+
'Les points de terminaison sont désactivés après cinq jours d’échecs au lieu de sept. Définissez `delivery.disable_after` pour conserver l’ancien comportement.',
|
|
683
|
+
]),
|
|
684
|
+
],
|
|
685
|
+
},
|
|
686
|
+
];
|
|
687
|
+
const FOOTER = [
|
|
688
|
+
{
|
|
689
|
+
heading: 'Premiers pas',
|
|
690
|
+
links: [
|
|
691
|
+
{ label: 'Introduction', url: '/docs/introduction' },
|
|
692
|
+
{ label: 'Installation', url: '/docs/installation' },
|
|
693
|
+
{ label: 'Démarrage rapide', url: '/docs/demarrage-rapide' },
|
|
694
|
+
],
|
|
695
|
+
},
|
|
696
|
+
{
|
|
697
|
+
heading: 'Guides',
|
|
698
|
+
links: [
|
|
699
|
+
{ label: 'Vérifier les signatures', url: '/docs/verifier-les-signatures' },
|
|
700
|
+
{ label: 'Réessais et rejeu', url: '/docs/reessais-et-rejeu' },
|
|
701
|
+
{ label: 'Déploiement en production', url: '/docs/deploiement-en-production' },
|
|
702
|
+
],
|
|
703
|
+
},
|
|
704
|
+
{
|
|
705
|
+
heading: 'Référence',
|
|
706
|
+
links: [
|
|
707
|
+
{ label: 'CLI', url: '/docs/reference-cli' },
|
|
708
|
+
{ label: 'Configuration', url: '/docs/reference-configuration' },
|
|
709
|
+
{ label: 'API HTTP', url: '/docs/api-http' },
|
|
710
|
+
],
|
|
711
|
+
},
|
|
712
|
+
{
|
|
713
|
+
heading: 'Aide',
|
|
714
|
+
links: [
|
|
715
|
+
{ label: 'Dépannage', url: '/docs/depannage' },
|
|
716
|
+
{ label: 'Nouveautés', url: '/docs/nouveautes' },
|
|
717
|
+
],
|
|
718
|
+
},
|
|
719
|
+
];
|
|
720
|
+
export const DOCUMENTATION_COPY_FR = {
|
|
721
|
+
locale: 'fr',
|
|
722
|
+
sections: { start: 'Premiers pas', guides: 'Guides', reference: 'Référence', help: 'Aide' },
|
|
723
|
+
pages: PAGES,
|
|
724
|
+
changelogSlug: 'nouveautes',
|
|
725
|
+
typography: frenchTypography,
|
|
726
|
+
home: {
|
|
727
|
+
slug: 'home',
|
|
728
|
+
title: (name) => `Documentation ${name}`,
|
|
729
|
+
blocks: (names, w) => {
|
|
730
|
+
const { name } = names;
|
|
731
|
+
const blocks = [
|
|
732
|
+
{
|
|
733
|
+
_key: 'home-hero',
|
|
734
|
+
_type: 'hero',
|
|
735
|
+
_version: BLOCK_VERSION,
|
|
736
|
+
eyebrow: `${name} 2.4`,
|
|
737
|
+
title: `Documentation ${name}`,
|
|
738
|
+
subtitle: 'Envoyez des webhooks signés depuis votre application, réessayez les livraisons en échec selon un calendrier et rejouez les événements des 30 derniers jours. Commencez par le démarrage rapide, ou cherchez dans les guides et la référence.',
|
|
739
|
+
actions: [
|
|
740
|
+
{
|
|
741
|
+
label: 'Démarrage rapide',
|
|
742
|
+
target: { href: '/docs/demarrage-rapide' },
|
|
743
|
+
emphasis: 'primary',
|
|
744
|
+
},
|
|
745
|
+
{ label: 'Référence de l’API HTTP', target: { href: '/docs/api-http' } },
|
|
746
|
+
],
|
|
747
|
+
},
|
|
748
|
+
{
|
|
749
|
+
_key: 'home-start',
|
|
750
|
+
_type: 'featureGrid',
|
|
751
|
+
_version: BLOCK_VERSION,
|
|
752
|
+
title: 'Par où commencer',
|
|
753
|
+
items: [
|
|
754
|
+
{
|
|
755
|
+
_key: 'home-start-install',
|
|
756
|
+
title: 'Installer la commande',
|
|
757
|
+
text: 'Node.js 20.11 ou plus récent, ou l’image de conteneur.',
|
|
758
|
+
link: { href: '/docs/installation' },
|
|
759
|
+
},
|
|
760
|
+
{
|
|
761
|
+
_key: 'home-start-quickstart',
|
|
762
|
+
title: 'Envoyer votre premier événement',
|
|
763
|
+
text: 'Un serveur local, un point de terminaison et une requête signée, en cinq minutes environ.',
|
|
764
|
+
link: { href: '/docs/demarrage-rapide' },
|
|
765
|
+
},
|
|
766
|
+
{
|
|
767
|
+
_key: 'home-start-concepts',
|
|
768
|
+
title: 'Comprendre les concepts',
|
|
769
|
+
text: 'Événements, points de terminaison, livraisons et tentatives, et ce qui se passe en cas d’échec.',
|
|
770
|
+
link: { href: '/docs/concepts-cles' },
|
|
771
|
+
},
|
|
772
|
+
],
|
|
773
|
+
},
|
|
774
|
+
{
|
|
775
|
+
_key: 'home-browse',
|
|
776
|
+
_type: 'collectionList',
|
|
777
|
+
_version: BLOCK_VERSION,
|
|
778
|
+
title: 'Parcourir la documentation',
|
|
779
|
+
collection: 'doc_page',
|
|
780
|
+
sort: { field: 'createdAt', direction: 'asc' },
|
|
781
|
+
limit: 100,
|
|
782
|
+
layout: 'list',
|
|
783
|
+
},
|
|
784
|
+
{
|
|
785
|
+
_key: 'home-send',
|
|
786
|
+
_type: 'prose',
|
|
787
|
+
_version: BLOCK_VERSION,
|
|
788
|
+
body: [
|
|
789
|
+
w.h2('Envoyer un événement depuis votre application'),
|
|
790
|
+
w.p('Une fois qu’un serveur tourne, votre application envoie des événements en HTTP. Chaque point de terminaison abonné au type d’événement reçoit sa propre copie signée.'),
|
|
791
|
+
w.code('send-event.ts', `const response = await fetch('http://localhost:8787/v1/events', {
|
|
792
|
+
method: 'POST',
|
|
793
|
+
headers: {
|
|
794
|
+
Authorization: 'Bearer ' + process.env.%ENV%_API_KEY,
|
|
795
|
+
'Content-Type': 'application/json',
|
|
796
|
+
},
|
|
797
|
+
body: JSON.stringify({
|
|
798
|
+
type: 'invoice.paid',
|
|
799
|
+
data: { invoice_id: 'in_1042', amount: 4900, currency: 'eur' },
|
|
800
|
+
}),
|
|
801
|
+
})
|
|
802
|
+
|
|
803
|
+
// 202 Accepted : l’événement est stocké et ses livraisons sont planifiées
|
|
804
|
+
const { id, deliveries } = await response.json()`),
|
|
805
|
+
w.p('La [référence de l’API HTTP](/docs/api-http) liste chaque ressource, avec des exemples de réponses et d’erreurs.'),
|
|
806
|
+
],
|
|
807
|
+
},
|
|
808
|
+
{
|
|
809
|
+
_key: 'home-faq',
|
|
810
|
+
_type: 'faq',
|
|
811
|
+
_version: BLOCK_VERSION,
|
|
812
|
+
title: 'Questions fréquentes',
|
|
813
|
+
items: [
|
|
814
|
+
{
|
|
815
|
+
_key: 'home-faq-databases',
|
|
816
|
+
question: `Quelles bases de données ${name} prend-il en charge ?`,
|
|
817
|
+
answer: [
|
|
818
|
+
w.p('SQLite pour le développement local avec `%cli% dev`, et PostgreSQL 14 ou plus récent pour `%cli% start` en production.'),
|
|
819
|
+
],
|
|
820
|
+
},
|
|
821
|
+
{
|
|
822
|
+
_key: 'home-faq-licence',
|
|
823
|
+
question: `${name} est-il open source ?`,
|
|
824
|
+
answer: [
|
|
825
|
+
w.p('Oui. Le serveur, la ligne de commande et les bibliothèques de signature sont publiés sous licence Apache 2.0.'),
|
|
826
|
+
],
|
|
827
|
+
},
|
|
828
|
+
{
|
|
829
|
+
_key: 'home-faq-retention',
|
|
830
|
+
question: 'Combien de temps les événements sont-ils conservés ?',
|
|
831
|
+
answer: [
|
|
832
|
+
w.p('30 jours par défaut, avec chaque tentative et le premier kilo-octet de chaque réponse. Modifiez cette durée avec `retention.events`.'),
|
|
833
|
+
],
|
|
834
|
+
},
|
|
835
|
+
{
|
|
836
|
+
_key: 'home-faq-outage',
|
|
837
|
+
question: 'Que se passe-t-il quand un point de terminaison est hors service pendant une journée ?',
|
|
838
|
+
answer: [
|
|
839
|
+
w.p('Ses livraisons sont réessayées pendant un peu plus de 31 heures. Passé ce délai, [relancez-les à la main ou rejouez-les](/docs/reessais-et-rejeu) une fois le point de terminaison rétabli.'),
|
|
840
|
+
],
|
|
841
|
+
},
|
|
842
|
+
],
|
|
843
|
+
},
|
|
844
|
+
{
|
|
845
|
+
_key: 'home-help',
|
|
846
|
+
_type: 'cta',
|
|
847
|
+
_version: BLOCK_VERSION,
|
|
848
|
+
title: 'Une livraison qui échoue',
|
|
849
|
+
text: 'La plupart des problèmes de livraison tiennent à une signature, à une horloge, ou à un point de terminaison qui répond par une redirection.',
|
|
850
|
+
actions: [
|
|
851
|
+
{ label: 'Dépannage', target: { href: '/docs/depannage' } },
|
|
852
|
+
{ label: 'Nouveautés de la 2.4', target: { href: '/docs/nouveautes' } },
|
|
853
|
+
],
|
|
854
|
+
},
|
|
855
|
+
];
|
|
856
|
+
return deepFrenchTypography(blocks);
|
|
857
|
+
},
|
|
858
|
+
},
|
|
859
|
+
diagram: {
|
|
860
|
+
file: 'documentation/delivery-flow-fr.png',
|
|
861
|
+
alt: 'Schéma : votre application envoie un événement au serveur de livraison, qui l’enregistre, signe une copie pour chaque point de terminaison et envoie trois requêtes ; deux points de terminaison répondent 200 et un répond 503 et sera réessayé dans deux minutes.',
|
|
862
|
+
},
|
|
863
|
+
menus: {
|
|
864
|
+
header: deepFrenchTypography([
|
|
865
|
+
{ label: 'Premiers pas', url: '/docs/demarrage-rapide' },
|
|
866
|
+
{ label: 'Guides', url: '/docs/verifier-les-signatures' },
|
|
867
|
+
{ label: 'Référence', url: '/docs/reference-cli' },
|
|
868
|
+
{ label: 'Nouveautés', url: '/docs/nouveautes' },
|
|
869
|
+
]),
|
|
870
|
+
},
|
|
871
|
+
footer: deepFrenchTypography(FOOTER),
|
|
872
|
+
settings: deepFrenchTypography({
|
|
873
|
+
'general.tagline': 'Livraison de webhooks signés pour votre application, avec réessais et rejeu.',
|
|
874
|
+
'general.socialLinks': [
|
|
875
|
+
{ label: 'GitHub', url: 'https://github.com/example' },
|
|
876
|
+
{ label: 'Bluesky', url: 'https://bsky.app/profile/example.com' },
|
|
877
|
+
{ label: 'X', url: 'https://x.com/example' },
|
|
878
|
+
],
|
|
879
|
+
'general.footerNote': 'Publié sous licence Apache 2.0.\nTexte de la documentation sous licence CC BY 4.0.',
|
|
880
|
+
// Une page de documentation n’est pas une discussion.
|
|
881
|
+
'discussion.enabled': false,
|
|
882
|
+
}),
|
|
883
|
+
widgets: (slugs) => {
|
|
884
|
+
const widgets = [
|
|
885
|
+
{
|
|
886
|
+
area: 'sidebar',
|
|
887
|
+
type: 'links',
|
|
888
|
+
title: 'Besoin d’aide ?',
|
|
889
|
+
settings: {
|
|
890
|
+
items: [
|
|
891
|
+
{ label: 'Dépannage', href: '/docs/depannage' },
|
|
892
|
+
{ label: 'Référence de configuration', href: '/docs/reference-configuration' },
|
|
893
|
+
{
|
|
894
|
+
label: 'Signaler un problème sur GitHub',
|
|
895
|
+
href: 'https://github.com/example',
|
|
896
|
+
newTab: true,
|
|
897
|
+
},
|
|
898
|
+
],
|
|
899
|
+
},
|
|
900
|
+
visibility: DOC_PAGES_ONLY,
|
|
901
|
+
},
|
|
902
|
+
{
|
|
903
|
+
area: 'sidebar',
|
|
904
|
+
type: 'cta',
|
|
905
|
+
settings: {
|
|
906
|
+
heading: 'Vous migrez depuis la 2.3 ?',
|
|
907
|
+
body: 'Lancez la migration avant de démarrer la 2.4. Elle ajoute un index et prend moins d’une minute sur dix millions de livraisons.',
|
|
908
|
+
label: 'Lire les notes de version',
|
|
909
|
+
href: '/docs/nouveautes',
|
|
910
|
+
},
|
|
911
|
+
visibility: {
|
|
912
|
+
pages: {
|
|
913
|
+
mode: 'only',
|
|
914
|
+
targets: slugs
|
|
915
|
+
.filter((slug) => slug !== 'nouveautes')
|
|
916
|
+
.map((slug) => ({ kind: 'path', path: `/docs/${slug}` })),
|
|
917
|
+
},
|
|
918
|
+
},
|
|
919
|
+
},
|
|
920
|
+
{
|
|
921
|
+
area: 'sidebar',
|
|
922
|
+
type: 'links',
|
|
923
|
+
title: 'Pages les plus lues',
|
|
924
|
+
settings: {
|
|
925
|
+
items: [
|
|
926
|
+
{ label: 'Démarrage rapide', href: '/docs/demarrage-rapide' },
|
|
927
|
+
{ label: 'Vérifier les signatures', href: '/docs/verifier-les-signatures' },
|
|
928
|
+
{ label: 'Réessais et rejeu', href: '/docs/reessais-et-rejeu' },
|
|
929
|
+
{ label: 'API HTTP', href: '/docs/api-http' },
|
|
930
|
+
{ label: 'Référence de la CLI', href: '/docs/reference-cli' },
|
|
931
|
+
],
|
|
932
|
+
},
|
|
933
|
+
visibility: { pages: { mode: 'only', targets: [{ kind: 'search' }] } },
|
|
934
|
+
},
|
|
935
|
+
];
|
|
936
|
+
return deepFrenchTypography(widgets);
|
|
937
|
+
},
|
|
938
|
+
};
|
|
939
|
+
//# sourceMappingURL=documentation-copy-fr.js.map
|