Aller au contenu

#Fichiers

C’est la route qui fait le travail : theme recuperer, theme pousser et theme suivre ne sont que des boucles autour d’elle. Elle lit une arborescence, lit un fichier, en écrit un, en supprime un — un seul à la fois, jamais le thème entier.

Deux onglets ouverts sur deux fichiers différents ne se marchent donc pas dessus, et une sauvegarde interrompue ne laisse pas un thème à moitié réécrit. Pour déplacer un thème complet, c’est Import et export.

L’<id> du chemin est celui d’un thème du site résolu. Un thème d’un autre site répond 404 introuvable, sur les trois méthodes.

#Lire l’arborescence

GET/api/themes/<id>/fichiers
200
json
{
  "theme": {
    "id": "11111111-1111-4111-8111-111111111111",
    "nom": "Origo",
    "role": "publie"
  },
  "fichiers": [
    {
      "chemin": "assets/theme.css",
      "octets": 18422,
      "empreinte": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
      "maj_le": "2026-08-05T09:12:44.108Z"
    },
    {
      "chemin": "config/settings_schema.json",
      "octets": 1204,
      "empreinte": "2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae",
      "maj_le": "2026-07-28T14:03:02.554Z"
    },
    {
      "chemin": "layout/theme.liquid",
      "octets": 3987,
      "empreinte": "486ea46224d1bb4fb680f34f7c9ad96a8f24ec88be73ea8e5a6c65260e9cb8a7",
      "maj_le": "2026-07-28T14:03:02.554Z"
    }
  ]
}
404 — thème inconnu, ou d’un autre site
json
{ "error": "introuvable" }

Portée lecture. Les chemins sont triés par ordre alphabétique, et le contenu n’est pas rendu : un thème complet pèse plusieurs centaines de kilo-octets, et l’éditeur n’affiche qu’un fichier à la fois.

themeobjet

id, nom et role du thème. role vaut publie ou brouillon — c’est ce que le CLI regarde avant de refuser de suivre un thème en ligne.

fichiers[].cheminchaîne

Toujours deux segments : dossier/fichier. Voir Arborescence pour les six dossiers et leurs extensions.

fichiers[].octetsentier

La taille réelle en octets UTF-8 du contenu. Un « é » y pèse 2.

fichiers[].empreintechaîne

Le SHA-256 hexadécimal du contenu, encodé en UTF-8. Calculé à la volée, jamais stocké.

fichiers[].maj_lehorodatage

Dernière écriture de ce fichier précis.

#À quoi sert l’empreinte

C’est elle qui rend theme pousser supportable. Le CLI calcule le SHA-256 de chacun de ses fichiers locaux, le compare à celui d’ici, et n’envoie que les différents. Sans elle, il faudrait télécharger le thème entier avant chaque envoi juste pour savoir quoi envoyer — quelques centaines de kilo-octets à chaque sauvegarde, pour au plus un fichier modifié.

Attention

octets ne peut pas jouer ce rôle. Corriger une faute de frappe, changer une couleur hexadécimale ou permuter deux lignes laisse le fichier exactement aussi long : un client qui se fierait à la taille sauterait précisément les modifications qu’on fait le plus souvent.

Elle n’est stockée nulle part, et c’est délibéré. Le contenu est de toute façon lu par cette requête — il faut bien le lire pour donner octets — tandis qu’une colonne d’empreinte en base serait une seconde source de vérité à tenir à jour à chaque écriture, donc une occasion de mentir le jour où on l’oublierait quelque part.

Ne pousser que ce qui a changébash
# L'arborescence distante, ramenée à « chemin empreinte »
curl -s "https://cms.webcosa.com/api/themes/$THEME/fichiers" \
  -H "Authorization: Bearer $WEBCOSA_CLE" \
  | jq -r '.fichiers[] | "\(.chemin) \(.empreinte)"' > distant.txt

# La même chose en local
find layout sections snippets assets config templates -type f \
  -exec shasum -a 256 {} \; | awk '{print $2, $1}' | sort > local.txt

#Lire un fichier

GET/api/themes/<id>/fichiers?chemin=sections/hero.liquid
200
json
{
  "fichier": {
    "chemin": "sections/hero.liquid",
    "contenu": "<section class=\"hero\">\n  <h1>{{ section.settings.titre }}</h1>\n</section>\n",
    "maj_le": "2026-08-05T09:12:44.108Z"
  }
}
404 — ce chemin n’existe pas dans ce thème
json
{ "error": "fichier_introuvable" }

Le paramètre chemin est comparé à l’identique, sans normalisation : pas de barre oblique de tête, pas de ./, la casse compte. Pensez à l’encoder — la barre oblique passe telle quelle dans une chaîne de requête, mais un espace ou un accent, non.

Notez les deux codes distincts : introuvable désigne le thème, fichier_introuvable le chemin. Un client qui les confondrait chercherait une erreur d’identifiant là où il n’y a qu’une faute de frappe dans un nom de section.

#Écrire un fichier

PUT/api/themes/<id>/fichiers

Corps de la requête

json
{
  "chemin": "sections/temoignages.liquid",
  "contenu": "<section class=\"temoignages\">…</section>\n{% schema %}\n{ \"name\": \"Témoignages\" }\n{% endschema %}\n"
}
200
json
{ "ok": true }
400 — erreur de syntaxe Liquid
json
{
  "error": "syntaxe",
  "message": "Liquid invalide — ligne 14, colonne 1 : la balise « for » n'est jamais refermée — il manque {% endfor %}.",
  "ligne": 14,
  "colonne": 1
}
400 — schéma de section invalide
json
{
  "error": "syntaxe",
  "message": "Le schéma de la section n'est pas un JSON valide — ligne 73, colonne 3 : il manque une virgule ou une accolade fermante après une valeur.",
  "ligne": 73,
  "colonne": 3
}
400 — chemin refusé
json
{ "error": "hors_dossier", "message": "Ce fichier n'est dans aucun dossier connu. Attendus : layout, sections, snippets, assets, config, templates." }
400 — plafond de fichiers
json
{ "error": "trop_de_fichiers", "message": "Un thème est limité à 120 fichiers." }
409 — le chemin est pris (avec siAbsent)
json
{ "error": "fichier_existe", "message": "Ce fichier existe déjà dans le thème : il n'a pas été remplacé." }
402 — site inactif
json
{ "error": "site_inactif", "etat": "expire", "message": "Ce site n'est plus actif : ses modifications sont bloquées. Reprends un abonnement depuis le CMS (Réglages → Abonnement)." }

Écriture idempotente : le chemin existe, il est remplacé ; il n’existe pas, il est créé — sauf avec siAbsent, plus bas, qui refuse de remplacer. Il n’y a pas de POST distinct, et il n’y a pas de version précédente à laquelle revenir.

cheminchaînerequis

De 3 à 120 caractères, exactement deux segments. Voir les refus ci-dessous.

contenuchaînerequis

Le fichier entier, en clair. Plafonné à 524 288 — attention, des unités de code UTF-16, pas des octets : un fichier plein d’accents passe la barre un peu plus tard qu’un ls -l ne le laisse croire. C’est la même mesure partout dans le produit, et elle est volontairement large.

Une chaîne vide est acceptée : un fichier peut être vidé sans être supprimé.

siAbsentbooléen

N’écrire que si le chemin est libre. Un fichier qui existe déjà avec un autre contenu répond 409 fichier_existe et n’est pas touché ; un fichier qui porte déjà exactement ce contenu répond 200 avec { "ok": true, "inchange": true }, sans rien écrire — rejouer la requête est donc sans effet. Absent ou false, le PUT remplace, comme ci-dessus.

C’est ce qu’emploie Cosa quand il ajoute une section à un thème : sa proposition annonce un fichier neuf, et ne doit jamais écraser celui qu’on aurait écrit sous le même nom entre-temps.

#Les quatre refus de chemin

errorCe qui l’a déclenché
traverseeLe chemin commence par /, contient .. ou une barre oblique inversée
hors_dossierPas exactement deux segments, ou premier segment inconnu
nomLe nom du fichier ne commence pas par une lettre ou un chiffre, ou contient autre chose que lettres, chiffres, points, tirets et tirets bas
extensionExtension non admise dans ce dossier — un .js dans assets/, un .liquid dans config/
Danger

traversee est la faille classique des archives, celle qui permet d’écrire ailleurs que là où on croit. Elle est vérifiée à l’entrée, pas au moment d’écrire : un seul endroit à relire.

#La syntaxe est vérifiée avant d’écrire

Un fichier Liquid mal formé casse la page où il est rendu. Il est donc analysé avant d’être enregistré, et le refus dit où et pourquoi : en français, position comprise.

C’est ce qui distingue un éditeur de code utilisable d’un champ de texte : on apprend l’erreur en enregistrant, pas en visitant son site.

ExtensionCe qui est vérifié
.jsonJSON.parse
.liquidL’analyse Liquid ; pour une section, le JSON de son {% schema %} aussi
.css, .svgRien. Un CSS invalide dégrade l’apparence, il ne casse pas le rendu
Attention

Le contrôle porte sur la syntaxe, pas sur le sens. {{ nawak }} passe — comme chez Shopify — et rend du vide. Aucun {% render %} n’est résolu à ce stade : appeler un snippet qui n’existe pas ne se voit qu’au rendu de la page.

Le refus porte deux champs en plus du message, quand l’analyseur sait où : ligne et colonne, comptées à partir de 1 dans le fichier entier — y compris pour une erreur du {% schema %}, dont la position est celle du fichier et non celle du bloc. Un JSON qui s’arrête trop tôt (une accolade jamais refermée) n’a pas de position : les deux champs sont alors absents. Le message reste la seule chose à afficher ; ligne et colonne servent à y emmener — c’est ce que fait l’éditeur de code du CMS.

Note

Le {% schema %} d’une section est validé depuis le 30 septembre 2026. Avant, un schéma au JSON malformé s’écrivait sans erreur, et la section perdait en silence tous ses réglages dans l’éditeur visuel — une panne qu’on ne reliait à rien. Un thème importé avant cette date peut encore en contenir un : le premier enregistrement du fichier le signalera.

#Le plafond de fichiers

120 par thème, et il n’est contrôlé qu’à la création. Un thème déjà au plafond reste modifiable — sinon on ne pourrait plus corriger celui qui dépasse. Autrement dit, PUT sur un chemin existant ne compte jamais.

curl -s -X PUT "https://cms.webcosa.com/api/themes/$THEME/fichiers" \-H "Authorization: Bearer $WEBCOSA_CLE" \-H "Content-Type: application/json" \-d "$(jq -n --arg c "$(cat sections/hero.liquid)" \ '{chemin:"sections/hero.liquid", contenu:$c}')"{"ok":true}

Une écriture réussie met aussi à jour le maj_le du thème, pas seulement celui du fichier : c’est lui qui ordonne la liste des thèmes.

#Supprimer un fichier

DELETE/api/themes/<id>/fichiers?chemin=snippets/vieux.liquid
200
json
{ "ok": true }
400 — paramètre chemin absent
json
{ "error": "invalid_input" }
409 — fichier indispensable
json
{
  "error": "fichier_requis",
  "message": "Ce fichier est indispensable au thème : il ne peut pas être supprimé."
}

Trois chemins sont protégés, et la liste est close :

Ce qui ne se supprime pas
layout/theme.liquid
templates/index.json
config/settings_schema.json

Sans coquille ni template, le thème ne rend plus rien et le site tombe. Ces trois fichiers se remplacent par un PUT, ils ne se retirent pas.

Note

Supprimer un chemin qui n’existe pas répond 200 ok. La suppression est idempotente : rejouer une requête après un délai réseau ne doit pas produire une erreur qui enverrait chercher un problème inexistant.

Le CLI, lui, ne supprime jamais par défaut : theme pousser signale les fichiers présents à distance et absents en local, sans les toucher. Il faut --supprimer, et une confirmation. Ces fichiers n’existent qu’en base — il n’y a ni corbeille ni version précédente.

#L’original d’un fichier

GET/api/themes/<id>/fichiers/origine?chemin=sections/hero.liquid
200
json
{
  "origine": { "id": "origo", "nom": "Origo", "version": "1.1.0" },
  "installee": "1.0.2",
  "contenu": "<section class=\"hero\">…"
}
404 — thème importé ou écrit à la main
json
{ "error": "sans_origine", "message": "Ce thème n'a pas d'original connu : il a été importé ou écrit à la main." }
404 — fichier ajouté à ce thème
json
{ "error": "absent_de_l_original", "message": "« sections/offre.liquid » n'existe pas dans Origo : il a été ajouté à ce thème." }

Portée lecture, mêmes gardes que les autres méthodes : un thème d’un autre site répond 404 introuvable. La réponse est le fichier tel que le catalogue le livre aujourd’hui — c’est ce que l’éditeur de code compare au fichier retouché, et ce qu’il remet d’un clic (« Restaurer l’original » n’est qu’un PUT de ce contenu : aucune écriture n’a sa route à elle).

origineobjet

Le thème du catalogue dont celui-ci est issu : son identifiant, son nom et sa version actuelle.

installeechaîne

La version installée sur ce site. Quand elle diffère de origine.version, le contenu rendu est celui de la version plus récente : restaurer, c’est aussi en prendre les nouveautés pour ce fichier.

Mise en cache une minute, en privé : le catalogue ne change qu’à un déploiement.

#L’ordre des contrôles

RangContrôleRefus
1Clé valable, portée suffisante, site accessible401, 403, 409
2Le thème est bien du site404 introuvable
3Site actif — écritures seulement402 site_inactif
4Corps ou paramètre conforme400 invalid_input
5Chemin admis par le format400 traversee, hors_dossier, nom, extension
6Syntaxe du fichier400 syntaxe
7Chemin déjà pris, avec siAbsent409 fichier_existe
8Plafond de fichiers, à la création400 trop_de_fichiers

Le 404 du thème passe avant la garde de facturation : un identifiant erroné sur un site suspendu répond 404, pas 402.