Format du fichier seed

Sur cette page

Les fichiers seed sont des documents JSON qui initialisent les sites EmDash. Ils définissent les collections, champs, taxonomies, menus, redirections, zones de widgets, paramètres du site et du contenu d’exemple optionnel.

Structure racine

{
	"$schema": "https://emdashcms.com/seed.schema.json",
	"version": "1",
	"meta": {},
	"settings": {},
	"collections": [],
	"taxonomies": [],
	"bylines": [],
	"menus": [],
	"redirects": [],
	"widgetAreas": [],
	"sections": [],
	"content": {}
}
ChampTypeRequisDescription
$schemastringNonURL du schéma JSON pour la validation éditeur
version"1"OuiVersion du format seed
metaobjectNonMétadonnées sur le seed
settingsobjectNonParamètres du site
collectionsarrayNonDéfinitions de collections
taxonomiesarrayNonDéfinitions de taxonomies
bylinesarrayNonDéfinitions de profils de byline
menusarrayNonMenus de navigation
redirectsarrayNonRègles de redirection
widgetAreasarrayNonDéfinitions de zones de widgets
sectionsarrayNonBlocs de contenu réutilisables
contentobjectNonEntrées de contenu d’exemple

Meta

Métadonnées optionnelles sur le seed :

{
	"meta": {
		"name": "Blog Starter",
		"description": "A simple blog with posts, pages, and categories",
		"author": "EmDash"
	}
}

Settings

Valeurs de configuration à l’échelle du site :

{
	"settings": {
		"title": "My Site",
		"tagline": "A modern CMS",
		"postsPerPage": 10,
		"dateFormat": "MMMM d, yyyy"
	}
}

Les paramètres sont appliqués à la table options avec le préfixe site:. L’assistant de configuration permet aux utilisateurs de modifier title et tagline.

Collections

Les définitions de collections créent des types de contenu dans la base de données :

{
	"collections": [
		{
			"slug": "posts",
			"label": "Posts",
			"labelSingular": "Post",
			"description": "Blog posts",
			"icon": "file-text",
			"supports": ["drafts", "revisions"],
			"fields": [
				{
					"slug": "title",
					"label": "Title",
					"type": "string",
					"required": true
				},
				{
					"slug": "content",
					"label": "Content",
					"type": "portableText"
				},
				{
					"slug": "featured_image",
					"label": "Featured Image",
					"type": "image"
				}
			]
		}
	]
}

Propriétés de collection

PropriétéTypeRequisDescription
slugstringOuiIdentifiant URL-safe (minuscules, tirets bas)
labelstringOuiNom d’affichage au pluriel
labelSingularstringNonNom d’affichage au singulier
descriptionstringNonDescription dans l’interface d’administration
iconstringNonNom d’icône Lucide
supportsarrayNonFonctionnalités : "drafts", "revisions"
fieldsarrayOuiDéfinitions de champs

Propriétés de champ

PropriétéTypeRequisDescription
slugstringOuiNom de colonne (minuscules, tirets bas)
labelstringOuiNom d’affichage
typestringOuiType de champ
requiredbooleanNonValidation : le champ doit avoir une valeur
uniquebooleanNonValidation : la valeur doit être unique
defaultValueanyNonValeur par défaut pour les nouvelles entrées
validationobjectNonRègles de validation supplémentaires
widgetstringNonWidget d’interface d’administration personnalisé
optionsobjectNonConfiguration spécifique au widget

Types de champs

TypeDescriptionStocké en
stringTexte courtTEXT
textTexte long (textarea)TEXT
numberValeur numériqueREAL
integerNombre entierINTEGER
booleanVrai/fauxINTEGER
dateValeur de dateTEXT (ISO 8601)
datetimeDate et heureTEXT (ISO 8601)
emailAdresse emailTEXT
urlURLTEXT
slugChaîne URL-safeTEXT
portableTextContenu texte richeJSON
imageRéférence d’imageJSON
fileRéférence de fichierJSON
jsonJSON arbitraireJSON
referenceRéférence vers une autre entréeTEXT

Taxonomies

Systèmes de classification pour le contenu :

{
	"taxonomies": [
		{
			"name": "category",
			"label": "Categories",
			"labelSingular": "Category",
			"hierarchical": true,
			"collections": ["posts"],
			"terms": [
				{ "slug": "news", "label": "News" },
				{ "slug": "tutorials", "label": "Tutorials" },
				{
					"slug": "advanced",
					"label": "Advanced Tutorials",
					"parent": "tutorials"
				}
			]
		},
		{
			"name": "tag",
			"label": "Tags",
			"labelSingular": "Tag",
			"hierarchical": false,
			"collections": ["posts"]
		}
	]
}

Propriétés de taxonomie

PropriétéTypeRequisDescription
namestringOuiIdentifiant unique
labelstringOuiNom d’affichage au pluriel
labelSingularstringNonNom d’affichage au singulier
hierarchicalbooleanOuiAutoriser les termes imbriqués (catégories) ou plats (tags)
collectionsarrayOuiCollections auxquelles cette taxonomie s’applique
termsarrayNonTermes prédéfinis

Propriétés de terme

PropriétéTypeRequisDescription
slugstringOuiIdentifiant URL-safe
labelstringOuiNom d’affichage
descriptionstringNonDescription du terme
parentstringNonSlug du terme parent (hiérarchique uniquement)

Menus de navigation modifiables depuis l’admin :

{
	"menus": [
		{
			"name": "primary",
			"label": "Primary Navigation",
			"items": [
				{ "type": "custom", "label": "Home", "url": "/" },
				{ "type": "page", "ref": "about" },
				{ "type": "custom", "label": "Blog", "url": "/posts" },
				{
					"type": "custom",
					"label": "External",
					"url": "https://example.com",
					"target": "_blank"
				}
			]
		}
	]
}

Types d’éléments de menu

TypeDescriptionChamps requis
customURL personnaliséeurl
pageLien vers une entrée de pageref
postLien vers une entrée d’articleref
taxonomyLien vers une archive de taxonomieref, collection
collectionLien vers une archive de collectioncollection

Propriétés d’élément de menu

PropriétéTypeDescription
typestringType d’élément (voir ci-dessus)
labelstringTexte d’affichage (auto-généré pour les refs page/article)
urlstringURL personnalisée (pour le type custom)
refstringID du contenu dans le seed (pour les types page/post)
collectionstringSlug de la collection
targetstring"_blank" pour une nouvelle fenêtre
titleAttrstringAttribut HTML title
cssClassesstringClasses CSS personnalisées
childrenarrayÉléments de menu imbriqués

Bylines

Les profils de byline sont distincts de la propriété (author_id). Définissez des identités de byline réutilisables une fois, puis référencez-les depuis les entrées de contenu.

{
	"bylines": [
		{
			"id": "editorial",
			"slug": "emdash-editorial",
			"displayName": "EmDash Editorial"
		},
		{
			"id": "guest",
			"slug": "guest-contributor",
			"displayName": "Guest Contributor",
			"isGuest": true
		}
	]
}
PropriétéTypeRequisDescription
idstringOuiID local au seed utilisé par content[].bylines
slugstringOuiSlug URL-safe de la byline
displayNamestringOuiNom affiché dans les templates et les API
biostringNonBio de profil optionnelle
websiteUrlstringNonURL de site web optionnelle
isGuestbooleanNonMarque la byline comme profil invité

Redirections

Règles de redirection pour préserver les URL anciennes après migration :

{
	"redirects": [
		{ "source": "/old-about", "destination": "/about" },
		{ "source": "/legacy-feed", "destination": "/rss.xml", "type": 308 },
		{
			"source": "/category/news",
			"destination": "/categories/news",
			"groupName": "migration"
		}
	]
}

Propriétés de redirection

PropriétéTypeRequisDescription
sourcestringOuiChemin source (doit commencer par /)
destinationstringOuiChemin de destination (doit commencer par /)
typenumberNonStatut HTTP : 301, 302, 307 ou 308
enabledbooleanNonSi la redirection est active (défaut : true)
groupNamestringNonLibellé de regroupement optionnel pour le filtrage admin

Zones de widgets

Régions de contenu configurables :

{
	"widgetAreas": [
		{
			"name": "sidebar",
			"label": "Main Sidebar",
			"description": "Appears on blog posts and pages",
			"widgets": [
				{
					"type": "component",
					"title": "Recent Posts",
					"componentId": "core:recent-posts",
					"props": { "count": 5 }
				},
				{
					"type": "menu",
					"title": "Quick Links",
					"menuName": "footer"
				},
				{
					"type": "content",
					"title": "About",
					"content": [
						{
							"_type": "block",
							"style": "normal",
							"children": [{ "_type": "span", "text": "Welcome to our site!" }]
						}
					]
				}
			]
		}
	]
}

Types de widgets

TypeDescriptionChamps requis
contentContenu texte richecontent (Portable Text)
menuAffiche un menumenuName
componentComposant enregistrécomponentId

Composants intégrés

ID du composantDescription
core:recent-postsListe des articles récents
core:categoriesListe des catégories
core:tagsNuage de tags
core:searchFormulaire de recherche
core:archivesArchives mensuelles

Sections

Blocs de contenu réutilisables que les éditeurs peuvent insérer dans les champs Portable Text via la commande slash /section :

{
	"sections": [
		{
			"slug": "hero-centered",
			"title": "Centered Hero",
			"description": "Full-width hero with centered heading and CTA button",
			"keywords": ["hero", "banner", "header", "landing"],
			"content": [
				{
					"_type": "block",
					"style": "h1",
					"children": [{ "_type": "span", "text": "Welcome to Our Site" }]
				},
				{
					"_type": "block",
					"children": [
						{ "_type": "span", "text": "Your compelling tagline goes here." }
					]
				}
			]
		}
	]
}

Propriétés de section

PropriétéTypeRequisDescription
slugstringOuiIdentifiant URL-safe
titlestringOuiNom d’affichage dans le sélecteur de sections
descriptionstringNonExplique quand utiliser cette section
keywordsarrayNonTermes de recherche pour trouver la section
contentarrayOuiBlocs Portable Text
sourcestringNon"theme" (défaut pour les seeds) ou "import"

Les sections issues des fichiers seed sont marquées source: "theme" et ne peuvent pas être supprimées depuis l’interface d’administration. Les éditeurs peuvent créer leurs propres sections (source: "user") et insérer n’importe quel type de section lors de l’édition du contenu.

Contenu

Contenu d’exemple organisé par collection :

{
	"content": {
		"posts": [
			{
				"id": "hello-world",
				"slug": "hello-world",
				"status": "published",
				"bylines": [
					{ "byline": "editorial" },
					{ "byline": "guest", "roleLabel": "Guest essay" }
				],
				"data": {
					"title": "Hello World",
					"content": [
						{
							"_type": "block",
							"style": "normal",
							"children": [{ "_type": "span", "text": "Welcome!" }]
						}
					],
					"excerpt": "Your first post."
				},
				"taxonomies": {
					"category": ["news"],
					"tag": ["welcome", "first-post"]
				}
			}
		],
		"pages": [
			{
				"id": "about",
				"slug": "about",
				"status": "published",
				"data": {
					"title": "About Us",
					"content": [
						{
							"_type": "block",
							"style": "normal",
							"children": [{ "_type": "span", "text": "About page content." }]
						}
					]
				}
			}
		]
	}
}

Propriétés d’entrée de contenu

PropriétéTypeRequisDescription
idstringOuiID local au seed pour les références
slugstringOuiSlug d’URL
statusstringNon"published" ou "draft" (défaut : "published")
dataobjectOuiValeurs des champs
bylinesarrayNonCrédits de byline ordonnés (byline, roleLabel optionnel)
taxonomiesobjectNonAssignations de termes par nom de taxonomie

Références de contenu

Référencez d’autres entrées de contenu en utilisant le préfixe $ref: :

{
	"data": {
		"related_posts": ["$ref:another-post", "$ref:third-post"]
	}
}

Le préfixe $ref: résout les ID du seed en ID de base de données lors du seeding.

Références média

Incluez des images depuis des URL :

{
	"data": {
		"featured_image": {
			"$media": {
				"url": "https://images.unsplash.com/photo-xxx",
				"alt": "Description of the image",
				"filename": "hero.jpg",
				"caption": "Photo by Someone"
			}
		}
	}
}

Incluez des images locales depuis .emdash/media/ :

{
	"data": {
		"featured_image": {
			"$media": {
				"file": "hero.jpg",
				"alt": "Description of the image"
			}
		}
	}
}

Propriétés média

PropriétéTypeRequisDescription
urlstringOui*URL distante à télécharger
filestringOui*Nom de fichier local dans .emdash/media/
altstringNonTexte alternatif pour l’accessibilité
filenamestringNonNom de fichier de remplacement
captionstringNonLégende du média

*Soit url soit file est requis, pas les deux.

Appliquer les seeds par programmation

Utilisez l’API de seed pour les outils CLI ou les scripts :

import { applySeed, validateSeed } from "emdash/seed";
import seedData from "./.emdash/seed.json";

const validation = validateSeed(seedData);
if (!validation.valid) {
	console.error(validation.errors);
	process.exit(1);
}

const result = await applySeed(db, seedData, {
	includeContent: true,
	onConflict: "skip",
	storage: myStorage,
	baseUrl: "http://localhost:4321",
});

console.log(result);
// {
//   collections: { created: 2, skipped: 0 },
//   fields: { created: 8, skipped: 0 },
//   taxonomies: { created: 2, terms: 5 },
//   bylines: { created: 2, skipped: 0 },
//   menus: { created: 1, items: 4 },
//   redirects: { created: 3, skipped: 0 },
//   widgetAreas: { created: 1, widgets: 3 },
//   settings: { applied: 3 },
//   content: { created: 3, skipped: 0 },
//   media: { created: 2, skipped: 0 }
// }

Options d’application

OptionTypeDéfautDescription
includeContentbooleanfalseCréer les entrées de contenu d’exemple
onConflictstring"skip""skip", "update" ou "error"
mediaBasePathstringChemin de base pour les fichiers médias locaux
storageStorageAdaptateur de stockage pour les uploads médias
baseUrlstringURL de base pour les URL des médias

Idempotence

Le seeding peut être exécuté plusieurs fois en toute sécurité. Comportement en cas de conflit par type d’entité :

EntitéComportement
CollectionIgnoré si le slug existe
ChampIgnoré si collection + slug existe
Définition de taxonomieIgnoré si le nom existe
Terme de taxonomieIgnoré si nom + slug existe
Profil de bylineIgnoré si le slug existe
MenuIgnoré si le nom existe
Éléments de menuRemplacés (le menu est recréé)
RedirectionIgnoré si la source existe
Zone de widgetsIgnoré si le nom existe
WidgetsRemplacés (la zone est recréée)
SectionIgnoré si le slug existe
ParamètresMis à jour (les paramètres sont faits pour changer)
ContenuIgnoré si le slug existe dans la collection

Validation

Les fichiers seed sont validés avant application :

import { validateSeed } from "emdash/seed";

const { valid, errors, warnings } = validateSeed(seedData);

if (!valid) {
	errors.forEach((e) => console.error(e));
}

warnings.forEach((w) => console.warn(w));

Vérifications de validation :

  • Les champs requis sont présents
  • Les slugs suivent les conventions de nommage (minuscules, tirets bas)
  • Les types de champs sont valides
  • Les références pointent vers du contenu existant
  • Les parents de termes hiérarchiques existent
  • Les chemins de redirection sont des URL locales sûres
  • Les sources de redirection sont uniques
  • Pas de slugs dupliqués au sein des collections

Commandes CLI

# Appliquer un fichier seed
npx emdash seed .emdash/seed.json

# Appliquer sans contenu d'exemple
npx emdash seed .emdash/seed.json --no-content

# Valider uniquement
npx emdash seed .emdash/seed.json --validate

# Exporter le schéma actuel comme seed
npx emdash export-seed > seed.json

# Exporter avec le contenu
npx emdash export-seed --with-content > seed.json

Prochaines étapes