Un paquet de site est une copie portable du modèle de contenu, du contenu, de l’historique éditorial, de la présentation, des paramètres et des fichiers médias d’un site EmDash. Importez un paquet de site pour déplacer un site vers un autre déploiement EmDash, y compris un déploiement qui utilise une autre base de données : SQLite, PostgreSQL ou Cloudflare D1.
Une importation écrit dans un nouveau site dont la zone de contenu est vide. EmDash vérifie l’ensemble du paquet avant d’écrire quoi que ce soit, exécute l’importation en petites étapes reprenables, relit le site importé et émet un reçu lorsque le résultat correspond au paquet.
Un paquet de site ne contient ni utilisateurs, ni identifiants, ni secrets. Il contient en revanche chaque entrée et chaque commentaire du site, y compris les adresses e-mail des auteurs et des commentateurs. Stockez-le et transmettez-le avec le même soin qu’une sauvegarde de base de données.
Choisir le bon type de copie
| Mécanisme | Objectif | Importable | Fichiers médias | Utilisateurs et secrets |
|---|---|---|---|---|
| Fichier seed | Initialiser un modèle de contenu et du contenu d’exemple | Oui, avec la sémantique des seeds | Non | Non |
| Instantané d’aperçu | Alimenter le rendu d’aperçu isolé | Aperçu uniquement | Non | Non |
| Sauvegarde JSON | Inspecter l’état sélectionné sous forme de base de données | Non | Non | Non |
| Sauvegarde brute de la base de données et des médias | Restaurer un déploiement | Restauration dans le même type de base de données | Copie séparée | Oui |
| Paquet de site | Déplacer un site vers un autre site EmDash | Oui, dans un site vide | Oui | Non. Uniquement les noms et adresses e-mail des auteurs |
Utilisez une sauvegarde brute de la base de données pour restaurer un déploiement après une perte de données. Utilisez un paquet de site pour créer une nouvelle copie d’un site ailleurs.
Contenu d’un paquet de site
Un paquet de site contient :
- les collections, les champs, les types de blocs avec toutes leurs versions, les définitions de taxonomies, les définitions de relations et les définitions de champs de byline ;
- chaque entrée de contenu dans chaque langue, y compris les brouillons, les entrées planifiées, les entrées dans la corbeille, l’historique des révisions et les groupes de traduction ;
- les termes de taxonomie et les attributions de termes, les bylines et les crédits, les références de contenu et les enregistrements SEO ;
- les menus et éléments de menu, les zones de widgets et les widgets, les sections et les redirections ;
- les commentaires et les réactions aux commentaires, sauf si l’export exclut les commentaires ;
- les dossiers de médias, les métadonnées des médias et les octets de chaque fichier média prêt ; et
- les paramètres de site portables listés ci-dessous.
Le paquet stocke les valeurs JSON, comme les champs JSON et le Portable Text, avec les clés d’objet triées. Une valeur importée peut donc lister ses clés dans un ordre différent de celui de l’origine. Pour le reste, les valeurs sont inchangées.
Paramètres portables
Seuls ces paramètres sont exportés : site:title, site:tagline, site:logo, site:favicon, site:postsPerPage, site:dateFormat, site:timezone, site:social, site:seo, emdash:site_title, emdash:site_tagline et emdash:locale.
Le site cible conserve sa propre URL de site (site:url et emdash:site_url), son ID de site, son état de configuration et ses paramètres de sauvegarde. Une importation ne les écrase jamais.
Le plan d’importation demande s’il faut conserver le titre et le slogan de la cible, écrits par l’assistant de configuration, ou utiliser les valeurs du paquet. Par défaut, les valeurs du paquet sont utilisées.
Principals
Un compte utilisateur n’est jamais déplacé avec un paquet. Pour chaque utilisateur d’origine auquel font référence le contenu, les révisions, les médias, les bylines ou les commentaires, le paquet contient un principal : l’ID de l’utilisateur, son nom d’affichage et son adresse e-mail. Un principal n’a ni rôle, ni mot de passe, ni passkey, ni session, ni jeton.
Pendant l’importation, vous associez chaque principal à un utilisateur du site cible ou le laissez sans association. Consultez associer les auteurs aux utilisateurs cibles.
Commentaires
Les commentaires comprennent le nom et l’adresse e-mail de l’auteur, le corps, le statut, le fil de discussion, les horodatages et les métadonnées de modération. Le hachage de l’adresse IP et l’user agent ne sont pas exportés.
Les réactions conservent leur nombre. L’exportateur remplace chaque hachage de votant par une nouvelle valeur aléatoire, de sorte que la cible ne peut pas associer une réaction au visiteur qui l’a émise.
Ce qu’un paquet de site exclut
Un paquet de site ne contient jamais :
- les utilisateurs, sessions, passkeys, comptes OAuth, domaines autorisés, jetons d’API, clients OAuth, codes d’autorisation ou codes d’appareil ;
- le stockage, l’état ou les paramètres des plugins, y compris les secrets des plugins ;
- les paramètres autres que les paramètres portables, comme le secret de signature des aperçus ;
- les journaux d’audit, les limites de débit, les verrous d’édition, l’état des tâches planifiées, le journal des erreurs 404 ou l’historique des migrations ;
- les enregistrements d’utilisation des médias et les index de recherche, que l’importation reconstruit ;
- les clés de stockage, noms de buckets, noms de bases de données ou noms de bindings de l’origine ; ni
- les médias qui ne sont pas prêts, comme un téléversement incomplet.
Les médias d’un fournisseur de médias externe restent externes. Le paquet conserve la référence, mais les fichiers du fournisseur ne sont pas copiés.
Préparer le site cible
Importez dans un site qui remplit toutes les conditions ci-dessous. Lorsque le contenu, les langues, la limite de téléversement ou le format pris en charge par la cible ne conviennent pas au paquet, l’analyse signale un bloqueur.
- Un compte administrateur. L’importation s’exécute en tant qu’administrateur connecté ou avec un jeton d’API. Créez l’administrateur de la cible pendant la configuration.
- Un backend de stockage. L’origine comme la cible ont besoin d’un stockage configuré. EmDash y place les fichiers du paquet en attente.
- Aucun contenu. La cible ne doit contenir aucune entrée (y compris les entrées dans la corbeille), révision, média ou dossier de médias, byline ou champ de byline, commentaire, redirection, attribution de terme, relation, enregistrement SEO, section créée dans l’administration, ni collection ou type de bloc créé après la configuration. Un site configuré à partir de n’importe quel template officiel convient. Ce que la configuration a créé constitue l’ossature de configuration : les collections et types de blocs issus du seed, les définitions de taxonomies et leurs termes non attribués, les menus et leurs éléments, les zones de widgets et leurs widgets, ainsi que les sections du thème. Le plan liste cette ossature, et l’importation la supprime une fois que vous avez confirmé le plan.
- Toutes les langues utilisées par le paquet. Ajoutez chacune des langues du paquet à la configuration i18n de la cible. Un site sans configuration i18n n’accepte que
en. Les langues sont comparées sans tenir compte de la casse, et l’importation écrit chaque langue avec la casse configurée sur la cible, ce qui est déclaré commelocale_recased. - Une limite de téléversement suffisante. Chaque fichier média doit tenir dans la
maxUploadSizede la cible, qui est de 50 Mio par défaut. - Version de format
1. La cible doit prendre en charge la version de format du paquet et chaque fonctionnalité requise.
La requête suivante renvoie les versions de format, les fonctionnalités et les limites prises en charge. Son objet portableDomain indique si le site peut recevoir une importation et, dans le cas contraire, pourquoi.
curl https://new.example.com/_emdash/api/admin/transfer/capabilities \
-H "Authorization: Bearer $EMDASH_TOKEN"
Exporter un site
Un export lit le site par étapes bornées et écrit le paquet dans le stockage du site. Avant la fin d’un export, l’exportateur valide le paquet terminé de la même manière qu’une importation. Lorsqu’une écriture sur le site réussit pendant un export, l’exportateur recommence. Prendre ou renouveler un verrou d’édition sur une entrée ne compte pas comme une écriture. Après trois tentatives, il échoue avec TRANSFER_EXPORT_CONCURRENT_WRITES.
Les fichiers de l’export restent disponibles pendant sept jours après la création de l’export. Passé ce délai, un téléchargement renvoie TRANSFER_EXPIRED.
Exporter depuis l’administration
-
Ouvrez Settings → Transfer. La page est accessible aux administrateurs.
-
Dans la section Export, désactivez Include comments pour exclure les commentaires et les réactions.
-
Sélectionnez Export site. La page affiche la progression de l’export. Gardez la page ouverte ; si vous la quittez, l’export reprend lorsque vous revenez.
-
Lorsque Export ready apparaît, sélectionnez Download package et choisissez où enregistrer le fichier
.emdash. La page indique combien de fichiers et d’octets ont été téléchargés, et Stop annule le téléchargement.
La section affiche également l’empreinte (digest) du paquet, le nombre d’enregistrements de chaque type et les exports récents du site, chacun avec son propre bouton de téléchargement jusqu’à son expiration.
Download package récupère l’export un fichier à la fois, vérifie la taille et l’empreinte SHA-256 de chaque fichier par rapport au manifeste, puis construit le fichier .emdash dans le navigateur ; cela fonctionne donc sur Cloudflare Workers pour des sites de toute taille. Si un fichier ne correspond pas, le téléchargement s’arrête avec une erreur. Chrome, Edge et les autres navigateurs basés sur Chromium écrivent le fichier directement sur le disque. Les autres navigateurs gardent l’intégralité du paquet en mémoire jusqu’à la fin du téléchargement ; pour un export de plus de 500 Mo environ, la page recommande un navigateur basé sur Chromium ou la CLI.
Download as one file demande plutôt au serveur l’archive en une seule réponse. Cette option convient aux petits sites. Sur Cloudflare Workers, un site volumineux peut dépasser les limites d’une seule requête.
Exporter avec la CLI
Connectez-vous au site d’origine, puis exportez-le dans un fichier de paquet :
npx emdash login --url https://example.com
npx emdash site export --url https://example.com --output site.emdash
La commande mène l’export à son terme, télécharge le paquet fichier par fichier, vérifie la taille et l’empreinte de chaque fichier et écrit site.emdash. Ajoutez --no-comments pour exclure les commentaires et les réactions. Si la commande est interrompue, relancez-la avec les mêmes options pour reprendre le même export. Consultez la référence de emdash site export.
Exporter avec l’API REST
Chaque appel à advance exécute une étape et renvoie nextRequestInMs, le délai avant l’appel suivant. L’export est terminé lorsque nextRequestInMs vaut null.
Ces exemples utilisent un jeton d’accès personnel doté de la portée transfer:export. Consultez portées de jeton.
-
Démarrez l’export. Pour exclure les commentaires et les réactions, envoyez
{ "comments": false }comme corps. Un en-têteIdempotency-Keyfait en sorte qu’une requête réessayée renvoie le même export au lieu d’en démarrer un autre. Réutiliser une clé avec des options différentes échoue avec409 TRANSFER_IDEMPOTENCY_CONFLICT.curl -X POST https://example.com/_emdash/api/admin/transfer/exports \ -H "Authorization: Bearer $EMDASH_TOKEN" \ -H "Idempotency-Key: move-to-new-host" -
Faites avancer l’export jusqu’à ce que
nextRequestInMsvaillenull. Attendez le nombre de millisecondes renvoyé entre les appels.operation.progressindique les étapesdoneettotal, lesrecordsécrits jusqu’ici, ainsi quebytesDoneetbytesTotalune fois la taille du paquet connue.curl -X POST https://example.com/_emdash/api/admin/transfer/exports/$EXPORT_ID/advance \ -H "Authorization: Bearer $EMDASH_TOKEN" -
Vérifiez que
operation.statevautcomplete. Un exportfailedindique la raison dansoperation.errorCode. -
Téléchargez le paquet sous la forme d’un seul fichier
.emdash:curl -o site.emdash \ https://example.com/_emdash/api/admin/transfer/exports/$EXPORT_ID/archive \ -H "Authorization: Bearer $EMDASH_TOKEN"
Un fichier .emdash est une archive tar non compressée dont la première entrée est manifest.json. L’archive diffuse tous les fichiers en une seule réponse. Sur Cloudflare Workers, un site volumineux peut dépasser les limites d’une seule requête. Téléchargez alors manifest.json depuis exports/{id}/manifest et chaque fichier depuis exports/{id}/files/{path}. Chaque fichier téléchargé est vérifié par rapport à son empreinte enregistrée pendant sa diffusion. Si les octets stockés ont changé après l’export, le téléchargement se termine par une erreur au lieu d’aboutir.
Importer un site
Une importation est créée à partir d’un paquet, analysée pour produire un plan, puis exécutée uniquement après que vous avez confirmé ce plan par son empreinte. Une importation dont l’exécution n’a pas commencé expire 24 heures après sa création.
L’administration, la CLI et l’API REST peuvent exécuter chaque étape. Un agent IA peut analyser et démarrer une importation déjà téléversée, au moyen des outils MCP.
Importer depuis l’administration
-
Sur le site cible, ouvrez Settings → Transfer. La section Import apparaît lorsque le site peut recevoir une importation. Sinon, elle liste ce que le site contient déjà et qui empêche une importation.
-
Sélectionnez Choose package file et choisissez le fichier
.emdash. Le navigateur vérifie le paquet et le téléverse en plusieurs parties. Rien ne change sur le site pendant le téléversement. Si le téléversement s’arrête, choisissez à nouveau le même fichier pour reprendre là où il s’était arrêté. -
Une fois le téléversement terminé, le site analyse le paquet. Vous pouvez quitter la page et revenir plus tard.
-
Examinez l’importation : le site source, la date d’export et la version d’EmDash, la taille, l’empreinte du paquet et le nombre d’enregistrements de chaque type. Lisez les Blockers et les Warnings, les Differences from the source site, qui listent les transformations du plan, et le Starter content that will be removed, regroupé par type. Consultez examiner le plan d’importation.
-
Sous Authors, choisissez l’utilisateur de ce site qui doit posséder le contenu de chaque auteur, ou Don’t map. Les auteurs qui correspondent à l’adresse e-mail d’un utilisateur sont marqués Matched by email. Consultez associer les auteurs aux utilisateurs cibles.
-
Sous Site identity, choisissez d’utiliser le titre et le slogan du site du paquet ou de conserver ceux de ce site.
-
Sélectionnez Start import et confirmez. Le bouton est désactivé tant que le plan comporte des bloqueurs. L’édition sur le site est suspendue jusqu’à la fin de l’importation.
-
Suivez la progression. Une fois l’importation terminée, la page affiche le reçu avec un badge Verified ainsi que ses empreintes de reçu, de paquet, de plan et de contenu. Sélectionnez Copy receipt pour conserver une copie du JSON du reçu.
La page propose également Cancel import depuis le téléversement jusqu’à la fin de l’importation, et Abandon import après l’échec ou l’annulation d’une importation qui avait commencé à écrire. Les deux demandent une confirmation. Consultez annuler une importation et abandonner une importation incomplète.
Importer avec la CLI
Connectez-vous au site cible, puis analysez le paquet :
npx emdash login --url https://new.example.com
npx emdash site import site.emdash --url https://new.example.com --analyze
La commande vérifie localement l’intégralité du fichier de paquet, le téléverse, l’analyse et affiche le plan avec son empreinte de plan. Elle se termine avec le code 2 lorsque le plan comporte des bloqueurs. Examinez le plan comme décrit dans examiner le plan d’importation.
Pour modifier les décisions du plan, relancez --analyze avec des options de décision. --map-principal associe un principal, par ID ou adresse e-mail, à un utilisateur cible par ID ou adresse e-mail, ou à none. --use-target-title et --use-target-tagline conservent le titre et le slogan de la cible :
npx emdash site import site.emdash --url https://new.example.com --analyze \
--map-principal editor@example.com=editor@example.com \
--map-principal 01J8ZQ4Y6T2N0D3VJ5R7K9M1PX=none \
--use-target-title
Exécutez le plan que vous avez examiné en passant son empreinte :
npx emdash site import site.emdash --url https://new.example.com \
--plan sha256:3f1c… --confirm
La commande mène l’importation à son terme et affiche le reçu. Si elle est interrompue, poursuivez-la avec emdash site import resume <operation-id>. emdash site import status <operation-id> affiche l’état de l’importation, et emdash site import receipt <operation-id> affiche à nouveau le reçu. Consultez la référence de emdash site import.
Importer avec l’API REST
Le serveur travaille avec les fichiers contenus dans un paquet, et non avec l’archive .emdash. Décompressez d’abord l’archive. Elle contient manifest.json, des fichiers d’index sous index/, des fichiers d’enregistrements sous records/ et des fichiers médias sous media/. Le manifeste fixe la taille et l’empreinte SHA-256 de chaque fichier, de sorte que l’empreinte du paquet identifie l’ensemble du paquet.
Ces exemples utilisent un jeton doté des portées transfer:analyze et transfer:execute.
-
Créez l’importation. Envoyez les octets inchangés de
manifest.jsoncomme corps de requête. La réponse contient l’opération et la première page des fichiers dont le serveur a encore besoin.curl -X POST https://new.example.com/_emdash/api/admin/transfer/imports \ -H "Authorization: Bearer $EMDASH_TOKEN" \ -H "Idempotency-Key: move-to-new-host" \ --data-binary @site/manifest.json -
Téléversez chaque fichier manquant vers
imports/{id}/files/{path}. L’en-têteContent-Lengthdoit être égal à la taille déclarée du fichier, et les octets doivent correspondre à son empreinte déclarée.curl -X PUT \ https://new.example.com/_emdash/api/admin/transfer/imports/$IMPORT_ID/files/index/000000.ndjson \ -H "Authorization: Bearer $EMDASH_TOKEN" \ --data-binary @site/index/000000.ndjsonTéléverser un fichier d’index déclare les fichiers d’enregistrements et de médias qu’il liste. Interrogez à nouveau
imports/{id}/missingaprès chaque lot de téléversements, et continuez jusqu’à ce qu’il ne renvoie plus aucun élément.Téléverser un fichier déjà stocké le vérifie à nouveau. Si la copie stockée ne correspond plus, le téléversement la remplace et la réponse indique
alreadyVerified: false. -
Analysez le paquet. Appelez
imports/{id}/analyzejusqu’à ce quenextRequestInMsvaillenull. La réponse finale contient leplanet sonplanDigest.curl -X POST https://new.example.com/_emdash/api/admin/transfer/imports/$IMPORT_ID/analyze \ -H "Authorization: Bearer $EMDASH_TOKEN" -
Examinez le plan et lisez chaque bloqueur, avertissement et transformation. Consultez examiner le plan d’importation.
-
Soumettez des décisions si les valeurs par défaut ne vous conviennent pas. Chaque soumission renvoie un nouveau plan et une nouvelle empreinte de plan. Une fois l’exécution demandée, le plan est figé et la soumission de décisions échoue avec
409 TRANSFER_INVALID_STATE.curl -X POST https://new.example.com/_emdash/api/admin/transfer/imports/$IMPORT_ID/analyze \ -H "Authorization: Bearer $EMDASH_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "decisions": { "principalMappings": { "01J8ZQ4Y6T2N0D3VJ5R7K9M1PX": null }, "siteTitle": "target" } }' -
Démarrez l’importation avec les empreintes que vous avez examinées :
curl -X POST https://new.example.com/_emdash/api/admin/transfer/imports/$IMPORT_ID/execute \ -H "Authorization: Bearer $EMDASH_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "packageDigest": "sha256:…", "planDigest": "sha256:…" }' -
Faites avancer l’importation jusqu’à ce que
nextRequestInMsvaillenull, en attendant le délai renvoyé entre les appels.curl -X POST https://new.example.com/_emdash/api/admin/transfer/imports/$IMPORT_ID/advance \ -H "Authorization: Bearer $EMDASH_TOKEN" -
Vérifiez que
operation.statevautcomplete, puis lisez le reçu depuisimports/{id}/receipt.
L’exécution échoue avec TRANSFER_PACKAGE_DIGEST_MISMATCH ou TRANSFER_PLAN_DIGEST_MISMATCH lorsque l’une des empreintes diffère du paquet en attente ou du plan actuel. Lisez le plan actuel depuis imports/{id}/plan, examinez-le à nouveau et réessayez avec ses empreintes.
Associer les auteurs aux utilisateurs cibles
L’analyse liste chaque principal avec son nom d’affichage, son adresse e-mail et le nombre d’enregistrements du paquet qui y font référence. Lorsqu’exactement un utilisateur cible possède la même adresse e-mail, comparée sans tenir compte de la casse, le plan suggère cet utilisateur et y associe le principal par défaut. Les principals sans suggestion commencent sans association.
Pour modifier une association, passez --map-principal à emdash site import --analyze, ou soumettez principalMappings au point de terminaison d’analyse. Chaque association désigne un utilisateur cible ou laisse le principal sans association (none dans la CLI, null dans l’API). EmDash applique chaque association aux auteurs d’entrées, aux auteurs de révisions, aux personnes ayant téléversé les médias, aux liens utilisateur des bylines et aux auteurs de commentaires.
Les références d’un principal sans association sont supprimées. Lorsqu’un auteur sans association avait également une byline liée à son compte, l’importateur crédite explicitement cette byline sur chacune des entrées de l’auteur qui n’a pas de crédit de byline explicite et dont la langue possède la byline de l’auteur. Le crédit de l’auteur reste donc sur la page.
Deux associations produisent un bloqueur principal_conflict :
- un principal possédant plus d’une byline dans la même langue est associé à un utilisateur ; ou
- deux principals ayant tous deux des bylines dans la même langue sont associés au même utilisateur.
Un utilisateur cible ne peut avoir qu’une seule byline par langue. Laissez un principal sans association, ou associez les principals à des utilisateurs différents.
Examiner le plan d’importation
Un plan liste ce que l’importation va créer, les décisions qu’elle va appliquer et trois types de constats :
- Les bloqueurs empêchent l’exécution. L’exécution renvoie
TRANSFER_PLAN_BLOCKEDtant que le plan en comporte. Modifiez les associations de principals pour résoudre unprincipal_conflict. Tout autre bloqueur nécessite une modification du paquet ou de la cible : annulez l’importation, effectuez la modification et créez une nouvelle importation. - Les avertissements décrivent des problèmes du paquet qui n’arrêtent pas l’importation. Ils sont recopiés dans le reçu.
- Les transformations sont les différences exactes et déclarées entre le site source et le site importé. Les modifications de l’exportateur sont listées en premier, puis celles de l’importation. La vérification applique les transformations de l’importation lorsqu’elle compare le site importé au paquet.
Un plan liste au maximum 500 bloqueurs et avertissements. Un avertissement issues_truncated indique combien d’autres ont été trouvés.
Bloqueurs
| Code | Signification |
|---|---|
package_invalid | Un fichier ou un chemin du paquet échoue à la validation. |
unsupported_format | La cible ne prend pas en charge le format ou la version de format du paquet. |
unsupported_feature | Le paquet nécessite une fonctionnalité que la cible ne prend pas en charge. |
limit_exceeded | Un fichier ou un enregistrement du paquet dépasse une limite. |
file_missing | Un fichier déclaré du paquet n’a pas été téléversé. |
file_mismatch | La taille ou l’empreinte d’un fichier du paquet ne correspond pas à sa déclaration. |
record_invalid | Un enregistrement est mal formé ou n’est pas du JSON canonique. |
record_count_mismatch | Le nombre d’enregistrements d’un type diffère du manifeste. |
record_order_invalid | Les enregistrements ne sont pas dans l’ordre, ou un parent apparaît après son enfant. |
duplicate_id | Deux enregistrements du même type partagent un ID. |
dangling_reference | Un enregistrement fait référence à un enregistrement absent du paquet. Cela inclut un champ de blocs qui nomme un type de bloc absent du paquet, et un type de bloc dont la version actuelle est absente du paquet. |
reference_cycle | Un terme, un commentaire ou un élément de menu est son propre parent. |
media_ref_invalid | Du contenu fait référence à un enregistrement de média absent du paquet. |
media_blob_missing | Le fichier d’un enregistrement de média est absent du paquet. |
media_blob_too_large | Un fichier média est plus volumineux que la maxUploadSize de la cible. |
target_not_empty | La cible contient déjà du contenu. Le detail du bloqueur indique ce qui a été trouvé. |
locale_not_configured | Le paquet utilise une langue que la configuration i18n de la cible n’inclut pas. |
field_type_unknown | Un champ ou un champ de byline utilise un type que la cible ne prend pas en charge. |
principal_conflict | Les associations de principals donneraient à un utilisateur deux bylines dans la même langue. |
integer_out_of_range | Un entier est en dehors de la plage d’entiers de la base de données cible. PostgreSQL stocke les entiers sur 32 bits. |
value_constraint_violation | Une valeur que l’API d’administration refuserait. Consultez la liste ci-dessous. |
unique_violation | Un enregistrement dupliquerait la clé unique d’un autre enregistrement sur la cible. |
L’importateur écrit directement les enregistrements ; l’analyse applique donc les mêmes contrôles que l’API d’administration lors de l’enregistrement de ces données. Chacune de ces valeurs est une value_constraint_violation :
- une valeur d’entrée qui ne tient pas dans la colonne de son champ, un champ obligatoire sans valeur, ou une valeur pour un champ que la collection ne possède pas ;
- une redirection dont la source ou la destination n’est pas un chemin du site, dont le type n’est pas pris en charge, dont le motif source est invalide, ou dont la destination utilise un paramètre que la source ne capture pas ;
- un site web de byline qui n’est pas une URL
httpouhttps, une valeur de champ de byline qui ne correspond pas au type ou aux choix de son champ, ou un champ de byline comportant plus de choix qu’un site n’en prend en charge ; - un motif d’URL de collection invalide ;
- un type de bloc avec un slug réservé, un libellé vide ou de plus de 200 caractères, ou des définitions de champs que l’éditeur de types de blocs refuserait ;
- une URL canonique SEO qui n’est ni une URL
httpouhttpsni un chemin du site ; et - une URL d’élément de menu avec un schéma que les menus n’autorisent pas.
Avertissements
| Code | Signification |
|---|---|
media_provider_external | Le contenu utilise des médias d’un fournisseur externe. La référence est conservée ; les fichiers ne sont pas copiés. |
media_row_missing | Un paramètre fait référence à un média absent du paquet. |
soft_reference_dangling | Une référence facultative ne se résout pas en un enregistrement du paquet. |
redirect_loops_unchecked | Le paquet comporte trop de redirections pour vérifier les boucles avant l’importation. Une redirection qui fermerait une boucle est importée désactivée. |
issues_truncated | Plus de bloqueurs ou d’avertissements ont été trouvés que le plan n’en liste. |
Transformations
L’exportateur déclare les modifications qu’il a apportées aux données du site source. Chacune de ces transformations comporte un type d’enregistrement et un nombre :
| Code | Signification |
|---|---|
orphan_dropped | Des enregistrements dont le parent n’existait plus sur le site source ont été exclus, par exemple une révision d’une entrée supprimée. |
soft_orphan_dropped | Des liens vers des enregistrements manquants ont été exclus, par exemple une attribution de terme vers un terme supprimé ou un élément de menu pointant vers une entrée supprimée. |
orphan_reference_nulled | Une référence à un enregistrement manquant a été supprimée, par exemple le dossier supprimé d’un fichier média. |
avatar_nulled | Un avatar de byline ou une image d’aperçu de section faisait référence à un média absent du paquet, et a été supprimé. |
media_not_ready_dropped | Des médias qui n’étaient pas prêts, comme un téléversement incomplet, ont été exclus. |
media_ref_unlinked | Des références à des médias absents du paquet ont été supprimées du contenu. |
media_url_relativized | Des URL absolues vers les propres fichiers médias du site source ont été converties en URL relatives au site, qui se résolvent sur la cible. |
redirect_duplicate_dropped | Des redirections en double pour le même chemin source ont été exclues. Une redirection par chemin source a été conservée. |
unknown_storage_key | Des enregistrements font encore référence à des fichiers médias que le site source ne possède pas. Ils ont été exportés sans modification. |
L’importation déclare ses propres modifications :
| Code | Signification |
|---|---|
principal_mapped | Les références aux principals sont réécrites vers les utilisateurs cibles associés. |
principal_unmapped | Les références aux principals sans association sont supprimées. |
seeded_scaffold_removed | L’ossature de configuration de la cible est supprimée avant que l’importation n’écrive. Le plan liste chaque élément. |
redirect_loop_disabled | Les redirections qui forment une boucle sont importées désactivées. |
search_unsupported | La recherche est désactivée pour les collections listées, car la cible utilise PostgreSQL. |
float4_rounded | Les valeurs décimales sont arrondies à la précision des colonnes real PostgreSQL de la cible. |
locale_recased | Les langues sont écrites avec la casse configurée sur la cible, par exemple pt-br en pt-BR. |
Exécuter l’importation
L’exécution parcourt ces phases dans l’ordre :
- Réserver la cible et vérifier à nouveau qu’elle est vide.
- Supprimer l’ossature de configuration listée dans le plan.
- Créer les types de blocs, les collections, les champs, les définitions de taxonomies, les définitions de relations et les champs de byline.
- Copier les fichiers médias dans le stockage de la cible et créer les enregistrements de médias.
- Écrire les termes et les bylines.
- Écrire les révisions et les entrées.
- Écrire les attributions de termes, les crédits de byline, les références de contenu et les enregistrements SEO.
- Écrire les menus, widgets, sections, redirections, commentaires, réactions et paramètres.
- Reconstruire les index de recherche et les caches, et mettre en file d’attente la réindexation de l’utilisation des médias.
- Vérifier le résultat.
Chaque appel à advance exécute une étape bornée, qui tient dans les limites de requête de Cloudflare Workers sur D1. La progression est stockée sur le serveur. Une requête interrompue perd au plus l’étape en cours, et chaque écriture est idempotente : relancer une étape ne duplique donc aucun enregistrement.
Pendant qu’une autre requête exécute une étape, ou lorsqu’une autre requête reprend l’opération pendant une étape, advance renvoie l’opération avec un nextRequestInMs court. Une erreur de stockage ou de base de données est réessayée : l’opération enregistre l’erreur, et nextRequestInMs augmente à chaque échec consécutif. Après des échecs répétés sans progression, l’importation échoue.
Les écritures sont bloquées pendant une importation
De la première étape d’exécution jusqu’à la fin de l’importation, EmDash rejette les requêtes d’écriture adressées à son API avec 503 TRANSFER_IMPORT_IN_PROGRESS. Cela concerne l’administration, l’API REST, les routes de plugins, les soumissions publiques de commentaires, la publication planifiée et les écritures de contenu par les plugins. La connexion, la gestion des utilisateurs et des jetons d’API, les verrous d’édition des entrées et l’API de transfert elle-même restent disponibles. Les requêtes de lecture ne sont pas bloquées.
Les outils MCP d’écriture, y compris les outils MCP des plugins, échouent avec TRANSFER_IMPORT_IN_PROGRESS dans l’erreur d’outil habituelle. Les outils MCP en lecture seule et les outils de transfert site_* continuent de fonctionner, de sorte qu’une importation démarrée via MCP peut être reprise, inspectée et terminée via MCP.
Reprendre après une interruption
La page d’administration ne fait avancer une importation que tant qu’elle est ouverte. Pour reprendre, rouvrez Settings → Transfer, exécutez emdash site import resume <operation-id> ou appelez à nouveau advance pour la même opération. Le serveur reprend à partir de la dernière étape terminée. Si la requête interrompue détenait encore l’opération, l’appel suivant attend l’expiration de cette détention, cinq minutes au maximum.
Une importation échouée ou annulée ne peut pas être reprise.
Annuler une importation
Sélectionnez Cancel import dans Settings → Transfer, exécutez emdash site import cancel <operation-id> ou envoyez POST imports/{id}/cancel. Une étape en cours s’arrête après son lot actuel. L’annulation ne supprime pas les enregistrements déjà écrits.
Abandonner une importation incomplète
Une importation échouée ou annulée qui avait commencé à écrire continue de bloquer les écritures, afin que le site incomplet ne puisse pas être modifié par erreur. Pour lever le blocage, sélectionnez Abandon import dans Settings → Transfer, exécutez emdash site import abandon <operation-id> ou envoyez POST imports/{id}/abandon. L’abandon conserve les données importées.
Après un abandon, le site n’est plus vide et ne peut donc pas recevoir une autre importation. Importez plutôt dans un site nouvellement configuré.
Une importation échouée ou annulée qui n’avait jamais commencé à écrire ne bloque pas les écritures et n’a pas besoin d’être abandonnée.
Vérifier le résultat
La vérification relit chaque enregistrement importé avec le même code que celui de l’exportateur, applique les transformations déclarées du plan aux enregistrements du paquet et compare les deux. Elle vérifie aussi le nombre d’enregistrements de chaque type et télécharge à nouveau chaque fichier média importé pour en vérifier l’empreinte. Toute différence fait échouer l’importation avec TRANSFER_VERIFICATION_FAILED. Le errorDetail de l’opération liste jusqu’à 50 de ces différences.
Une importation réussie produit un reçu :
{
"operationId": "01J8ZR2C4S6D8F0G2H4J6K8M0N",
"packageDigest": "sha256:…",
"planDigest": "sha256:…",
"targetSiteId": "01J8ZR0A2B4C6D8E0F2G4H6J8K",
"originSiteId": "01J1A3C5E7G9J1L3N5Q7S9U1W3",
"formatVersion": "1",
"importerEmDashVersion": "0.38.0",
"completedAt": "2026-09-23T10:15:00.000Z",
"logicalDigest": "sha256:…",
"counts": { "entry": 412, "media": 96 },
"warnings": [],
"verification": "verified",
"receiptDigest": "sha256:…"
}
Un reçu atteste que le site cible identifié par targetSiteId contenait exactement le contenu du paquet identifié par packageDigest, après application du plan identifié par planDigest, au moment où la vérification s’est terminée. Le logicalDigest résume les enregistrements vérifiés.
receiptDigest est l’empreinte SHA-256 du JSON canonique du reçu, sans la propriété receiptDigest. Elle permet de détecter un reçu modifié après son émission. Un reçu n’est pas signé et ne prouve donc pas quel serveur l’a émis. Récupérez le reçu depuis la cible via une connexion authentifiée lorsque cela compte.
Un reçu décrit le site au moment où la vérification s’est terminée. Il ne dit rien des modifications ultérieures.
Passer d’une base de données à une autre
Un paquet ne dépend pas de la base de données de l’origine. Exportez depuis SQLite, PostgreSQL ou D1 et importez dans n’importe laquelle d’entre elles. Prévoyez les différences suivantes lorsque la cible utilise PostgreSQL :
- PostgreSQL stocke les entiers sur 32 bits. Un entier en dehors de cette plage est un bloqueur
integer_out_of_range. - PostgreSQL stocke les champs
numberet les points focaux des médias sous forme de nombres à virgule flottante sur 32 bits. Les valeurs qui changent sont déclarées commefloat4_rounded, et la vérification compare les valeurs arrondies. - La recherche plein texte n’est disponible que sur SQLite et D1. Les collections dont la recherche est activée sont importées avec la recherche désactivée et déclarées comme
search_unsupported.
L’importateur écrit les médias dans le backend de stockage de la cible sous de nouvelles clés de stockage et réécrit en conséquence les références aux médias dans le contenu, les paramètres et les enregistrements SEO. Une référence à un fichier média que l’origine ne possède pas est exportée sans modification et déclarée comme unknown_storage_key.
Sécurité
- Traitez un paquet comme sensible. Il contient tout le contenu, y compris les brouillons et la corbeille, ainsi que les adresses e-mail des auteurs et des commentateurs. Tenez-le à l’écart des buckets publics et des dossiers partagés, et supprimez les copies dont vous n’avez plus besoin.
- Traitez un paquet comme une entrée non fiable. L’importation vérifie les chemins, les tailles, les empreintes, les schémas d’enregistrements, les références et les limites avant d’écrire. Elle n’exécute jamais de code ni de SQL provenant d’un paquet et ne récupère jamais d’URL qu’il contient.
- Accordez l’accès au transfert de manière réfléchie. Le transfert nécessite le rôle d’administrateur. Un jeton doté de la portée
adminpeut exécuter toutes les actions de transfert ; ne donnez donc au jeton d’un agent que la portée de transfert dont il a besoin. - Examinez le journal d’audit. EmDash consigne les actions de transfert dans le journal d’audit du site :
transfer_export_create,transfer_import_create,transfer_import_execute,transfer_import_cancel,transfer_import_abandon,transfer_import_complete,transfer_import_fail,transfer_approval_approveettransfer_approval_deny. Chaque entrée indique l’utilisateur à l’origine de l’action et l’opération ou l’approbation concernée (type de ressourcetransfer_operationoutransfer_approval). Ses détails ne contiennent que des ID, des empreintes, des nombres d’enregistrements et des codes d’erreur, jamais le contenu du paquet. De même, les détails des erreurs de transfert n’incluent jamais le contenu du paquet. - Gardez la zone d’attente privée. EmDash place les fichiers du paquet sous le préfixe
transfers/de votre bucket de stockage et refuse de servir ce préfixe via sa route de médias. Si le bucket dispose d’un domaine public, limitez-le aux médias, comme pour les sauvegardes. Les fichiers en attente sont supprimés à la fin ou à l’expiration d’une opération.
Portées de jeton
Le transfert utilise trois portées de jeton d’API :
| Portée | Autorise |
|---|---|
transfer:export | Démarrer, faire avancer et télécharger des exports. |
transfer:analyze | Créer des importations, téléverser des fichiers de paquet, analyser et lire des plans. |
transfer:execute | Démarrer, faire avancer, annuler et abandonner des importations. |
La portée admin inclut les trois ; le jeton enregistré par emdash login peut donc exécuter tout transfert. Chaque portée de transfert n’accorde que ses propres actions, et seul un administrateur peut en émettre une. Utilisez-les pour donner à un jeton un accès plus restreint que admin, par exemple à un agent qui peut analyser des paquets mais ni exporter ni importer. Consultez la référence des portées.
Approbations pour les agents
Les agents IA pilotent les transferts au moyen des outils MCP site_*. Ces outils démarrent les opérations, les font avancer et en rendent compte. Ils ne transportent jamais les octets du paquet : l’utilisateur d’un agent télécharge donc les exports et téléverse les paquets avec la CLI ou l’API REST. Chaque outil nécessite le rôle Admin.
Un client MCP dont le jeton ne possède ni admin ni la portée de transfert correspondante, comme un agent qui n’a reçu que transfer:analyze, ne peut pas démarrer seul un export ou une importation. Son appel à site_export_start ou site_import_start crée une demande d’approbation en attente et échoue avec TRANSFER_APPROVAL_REQUIRED et l’ID de l’approbation. Un administrateur approuve ou refuse la demande sous Approval requests dans Settings → Transfer, qui liste chaque demande en attente avec son demandeur, son action et son heure d’expiration. Les points de terminaison réservés aux sessions POST /_emdash/api/admin/transfer/approvals/{id}/approve et …/deny font de même. Les jetons d’API ne peuvent pas approuver de demandes. Le client répète ensuite l’appel avec l’ID de l’approbation. Les approbations ne s’appliquent qu’à ces outils MCP ; l’API REST n’a pas de paramètre d’approbation.
Une approbation accorde un appel à l’utilisateur qui l’a demandée, depuis le même jeton et avec les mêmes arguments. Une approbation d’export est liée aux options d’export. Une approbation d’importation est liée à l’opération et aux deux empreintes ; un plan modifié nécessite donc une nouvelle approbation. Une demande en attente expire au bout de 15 minutes, et une demande approuvée 15 minutes après l’approbation. La nouvelle tentative qui démarre l’opération la consomme ; si l’opération ne parvient pas à démarrer, la même approbation peut être réessayée jusqu’à son expiration. Le même utilisateur et le même jeton peuvent ensuite consulter et faire avancer cette opération sans la portée.
N’accordez transfer:export, transfer:execute ou admin au jeton d’un agent que lorsque l’agent doit exécuter des transferts sans qu’une personne n’approuve chacun d’eux.
Limites
| Limite | Valeur |
|---|---|
manifest.json | 8 Mio |
| Un enregistrement | 1 900 000 octets |
| Un fichier d’enregistrements ou d’index | 4 Mio et 1 000 enregistrements |
| Enregistrements par paquet | 5 000 000 |
| Fichiers par paquet | 1 000 000 |
| Profondeur d’imbrication JSON | 64 |
| Un fichier média | La maxUploadSize de la cible, 50 Mio par défaut |
Le point de terminaison capabilities indique les valeurs que le site applique.
Pour les hébergeurs
Un plan de contrôle d’hébergement peut faire passer le site d’un client en production uniquement avec l’API REST :
-
Provisionnez un nouveau site EmDash avec son stockage, ses langues et sa
maxUploadSize, puis terminez la configuration. Vérifiez quecapabilitiesindiqueportableDomain.emptyàtrue. -
Émettez un jeton pour le plan de contrôle avec
transfer:analyzeettransfer:execute. Tenez-le à l’écart de tout agent ou outil de création de site. -
Exécutez l’importation et appliquez votre propre politique aux avertissements du plan avant l’exécution. Refusez tout plan comportant des bloqueurs.
-
Récupérez le reçu et vérifiez-le avant de promouvoir le site :
verificationvautverified;packageDigestest l’empreinte du paquet que vous vouliez publier ;planDigestest le plan que vous avez accepté ;targetSiteIdest le site que vous vous apprêtez à promouvoir ; etreceiptDigestcorrespond au JSON canonique du reçu.
-
Promouvez le site, par exemple en faisant pointer son domaine vers lui.
Gardez la cible inaccessible tant que l’étape 4 n’a pas réussi. EmDash ne masque pas aux visiteurs un site partiellement importé.
Dépannage
Les erreurs de transfert utilisent des codes stables. Le statut HTTP figure à côté de chaque code.
| Code | Statut | Que faire |
|---|---|---|
TRANSFER_TARGET_NOT_EMPTY | 409 | La cible contient déjà du contenu. Importez dans un site nouvellement configuré. Settings → Transfer et capabilities listent ce qui rend le site inéligible. |
TRANSFER_IMPORT_IN_PROGRESS | 503 | Une importation est en cours sur ce site, ou une importation incomplète bloque encore les écritures. Attendez qu’elle se termine, ou abandonnez une importation échouée ou annulée. |
TRANSFER_FENCE_CHECK_FAILED | 503 | EmDash n’a pas pu vérifier si une importation est en cours. Réessayez l’écriture. |
TRANSFER_EXPORT_CONCURRENT_WRITES | 409 | Le site n’a cessé de changer pendant l’export. Exportez à nouveau lorsque l’activité d’édition est calme. |
TRANSFER_EXPIRED | 410 | Les fichiers de l’export ont été supprimés après sept jours, ou une importation n’a pas été exécutée dans les 24 heures. Recommencez. |
TRANSFER_FILE_MISSING | 422 | Certains fichiers déclarés n’ont pas été téléversés. Téléversez tout ce que liste imports/{id}/missing. |
TRANSFER_FILE_NOT_DECLARED | 422 | Le chemin de téléversement n’appartient pas au paquet. Ne téléversez que les chemins listés. |
TRANSFER_FILE_SIZE_MISMATCH | 422 | Content-Length ou les octets téléversés diffèrent de la taille déclarée. Téléversez le fichier sans le modifier. |
TRANSFER_FILE_DIGEST_MISMATCH | 422 | Les octets téléversés diffèrent de l’empreinte déclarée, ou un fichier d’export a changé après l’export. Téléversez le fichier d’origine ou exportez à nouveau. |
TRANSFER_LIMIT_EXCEEDED | 413 | Un fichier dépasse une limite. Pour les médias, augmentez la maxUploadSize de la cible. |
TRANSFER_MANIFEST_INVALID | 422 | Le corps de la requête n’est pas un manifeste valide. Envoyez manifest.json octet pour octet. |
TRANSFER_UNSUPPORTED_FORMAT | 422 | Mettez à niveau EmDash sur la cible. |
TRANSFER_UNSUPPORTED_FEATURE | 422 | Mettez à niveau EmDash sur la cible. |
TRANSFER_CONTAINER_INVALID | 422 | Le fichier .emdash n’est pas une archive de paquet valide. Téléchargez-le à nouveau. |
TRANSFER_PLAN_BLOCKED | 409 | Le plan comporte des bloqueurs. Consultez examiner le plan d’importation. |
TRANSFER_PACKAGE_DIGEST_MISMATCH | 409 | L’empreinte ne correspond pas au paquet en attente. Utilisez le packageDigest de l’opération. |
TRANSFER_PLAN_DIGEST_MISMATCH | 409 | Le plan a changé depuis que vous l’avez examiné. Lisez le plan actuel et examinez-le à nouveau. |
TRANSFER_DECISIONS_INVALID | 422 | Une décision désigne un principal inconnu ou un utilisateur cible qui n’existe pas. Corrigez l’association. |
TRANSFER_INVALID_STATE | 409 | L’opération n’est pas dans un état qui permet la requête. Lisez l’opération et suivez son state. |
TRANSFER_LEASE_ACTIVE | 409 | Une autre requête exécute une étape. Attendez et réessayez. |
TRANSFER_IDEMPOTENCY_CONFLICT | 409 | La Idempotency-Key a déjà été utilisée pour un export avec d’autres options, ou pour l’importation d’un autre paquet. Utilisez une nouvelle clé. |
TRANSFER_RUNTIME_MISMATCH | 409 | Une version incompatible d’EmDash a démarré l’opération. Terminez-la avec la version qui l’a démarrée, ou démarrez-en une nouvelle. |
TRANSFER_VERIFICATION_FAILED | 422 | Le site importé ne correspond pas au paquet. Lisez les différences dans errorDetail, abandonnez l’importation et importez dans un nouveau site. |
TRANSFER_APPROVAL_REQUIRED | 403 | Un administrateur doit approuver la demande. Consultez approbations pour les agents. |
TRANSFER_APPROVAL_INVALID | 403 | L’approbation est inconnue, refusée, expirée, déjà utilisée ou liée à d’autres paramètres. Demandez-en une nouvelle. |
TRANSFER_SCHEMA_UNCLASSIFIED | 500 | La base de données contient une table ou une colonne que l’exportateur ne reconnaît pas. Exécutez la version d’EmDash qui correspond aux migrations de la base de données. |
INSUFFICIENT_SCOPE | 403 | Le jeton ne possède ni admin ni la portée de transfert dont la requête a besoin. Émettez un jeton doté de cette portée. |