Os ficheiros seed são documentos JSON que inicializam sites EmDash. Definem coleções, campos, taxonomias, menus, redirecionamentos, áreas de widgets, configurações do site e conteúdo de exemplo opcional.
Estrutura Raiz
{
"$schema": "https://emdashcms.com/seed.schema.json",
"version": "1",
"meta": {},
"settings": {},
"collections": [],
"taxonomies": [],
"bylines": [],
"menus": [],
"redirects": [],
"widgetAreas": [],
"sections": [],
"content": {}
}
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
$schema | string | Não | URL do esquema JSON para validação no editor |
version | "1" | Sim | Versão do formato seed |
meta | object | Não | Metadados sobre o seed |
settings | object | Não | Configurações do site |
collections | array | Não | Definições de coleções |
taxonomies | array | Não | Definições de taxonomias |
bylines | array | Não | Definições de perfis de autoria |
menus | array | Não | Menus de navegação |
redirects | array | Não | Regras de redirecionamento |
widgetAreas | array | Não | Definições de áreas de widgets |
sections | array | Não | Blocos de conteúdo reutilizáveis |
content | object | Não | Entradas de conteúdo de exemplo |
Meta
Metadados opcionais sobre o seed:
{
"meta": {
"name": "Blog Starter",
"description": "A simple blog with posts, pages, and categories",
"author": "EmDash"
}
}
Configurações
Valores de configuração de todo o site:
{
"settings": {
"title": "My Site",
"tagline": "A modern CMS",
"postsPerPage": 10,
"dateFormat": "MMMM d, yyyy"
}
}
As configurações são aplicadas à tabela options com o prefixo site:. O Assistente de Configuração permite que os utilizadores alterem title e tagline.
Coleções
As definições de coleção criam tipos de conteúdo na base de dados:
{
"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"
}
]
}
]
}
Propriedades da Coleção
| Propriedade | Tipo | Obrigatório | Descrição |
|---|---|---|---|
slug | string | Sim | Identificador seguro para URL (minúsculas, underscores) |
label | string | Sim | Nome de exibição plural |
labelSingular | string | Não | Nome de exibição singular |
description | string | Não | Descrição no painel de administração |
icon | string | Não | Nome do ícone Lucide |
supports | array | Não | Funcionalidades: "drafts", "revisions" |
fields | array | Sim | Definições de campos |
Propriedades do Campo
| Propriedade | Tipo | Obrigatório | Descrição |
|---|---|---|---|
slug | string | Sim | Nome da coluna (minúsculas, underscores) |
label | string | Sim | Nome de exibição |
type | string | Sim | Tipo de campo |
required | boolean | Não | Validação: o campo deve ter um valor |
unique | boolean | Não | Validação: o valor deve ser único |
defaultValue | any | Não | Valor padrão para novas entradas |
validation | object | Não | Regras de validação adicionais |
widget | string | Não | Substituição de widget no admin |
options | object | Não | Configuração específica do widget |
Tipos de Campo
| Tipo | Descrição | Armazenado Como |
|---|---|---|
string | Texto curto | TEXT |
text | Texto longo (textarea) | TEXT |
number | Valor numérico | REAL |
integer | Número inteiro | INTEGER |
boolean | Verdadeiro/falso | INTEGER |
date | Valor de data | TEXT (ISO 8601) |
datetime | Data e hora | TEXT (ISO 8601) |
email | Endereço de e-mail | TEXT |
url | URL | TEXT |
slug | String segura para URL | TEXT |
portableText | Conteúdo rich text | JSON |
image | Referência a imagem | JSON |
file | Referência a ficheiro | JSON |
json | JSON arbitrário | JSON |
reference | Referência a outra entrada | TEXT |
Taxonomias
Sistemas de classificação para conteúdo:
{
"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"]
}
]
}
Propriedades da Taxonomia
| Propriedade | Tipo | Obrigatório | Descrição |
|---|---|---|---|
name | string | Sim | Identificador único |
label | string | Sim | Nome de exibição plural |
labelSingular | string | Não | Nome de exibição singular |
hierarchical | boolean | Sim | Permitir termos aninhados (categorias) ou planos (tags) |
collections | array | Sim | Coleções às quais esta taxonomia se aplica |
terms | array | Não | Termos pré-definidos |
Propriedades do Termo
| Propriedade | Tipo | Obrigatório | Descrição |
|---|---|---|---|
slug | string | Sim | Identificador seguro para URL |
label | string | Sim | Nome de exibição |
description | string | Não | Descrição do termo |
parent | string | Não | Slug do termo pai (apenas hierárquico) |
Menus
Menus de navegação editáveis a partir do 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"
}
]
}
]
}
Tipos de Item de Menu
| Tipo | Descrição | Campos Obrigatórios |
|---|---|---|
custom | URL personalizado | url |
page | Link para uma entrada de página | ref |
post | Link para uma entrada de post | ref |
taxonomy | Link para um arquivo de taxonomia | ref, collection |
collection | Link para um arquivo de coleção | collection |
Propriedades do Item de Menu
| Propriedade | Tipo | Descrição |
|---|---|---|
type | string | Tipo de item (ver acima) |
label | string | Texto de exibição (gerado auto. para refs de página/post) |
url | string | URL personalizado (para tipo custom) |
ref | string | ID do conteúdo no seed (para tipos page/post) |
collection | string | Slug da coleção |
target | string | "_blank" para nova janela |
titleAttr | string | Atributo HTML title |
cssClasses | string | Classes CSS personalizadas |
children | array | Itens de menu aninhados |
Bylines
Os perfis de autoria são separados da propriedade (author_id). Defina identidades de autoria reutilizáveis uma vez e depois referencie-as a partir de entradas de conteúdo.
{
"bylines": [
{
"id": "editorial",
"slug": "emdash-editorial",
"displayName": "EmDash Editorial"
},
{
"id": "guest",
"slug": "guest-contributor",
"displayName": "Guest Contributor",
"isGuest": true
}
]
}
| Propriedade | Tipo | Obrigatório | Descrição |
|---|---|---|---|
id | string | Sim | ID local do seed usado por content[].bylines |
slug | string | Sim | Slug de autoria seguro para URL |
displayName | string | Sim | Nome mostrado em templates e APIs |
bio | string | Não | Bio de perfil opcional |
websiteUrl | string | Não | URL de website opcional |
isGuest | boolean | Não | Marca o perfil de autoria como convidado |
Redirecionamentos
Regras de redirecionamento para preservar URLs legados após migração:
{
"redirects": [
{ "source": "/old-about", "destination": "/about" },
{ "source": "/legacy-feed", "destination": "/rss.xml", "type": 308 },
{
"source": "/category/news",
"destination": "/categories/news",
"groupName": "migration"
}
]
}
Propriedades do Redirecionamento
| Propriedade | Tipo | Obrigatório | Descrição |
|---|---|---|---|
source | string | Sim | Caminho de origem (deve começar com /) |
destination | string | Sim | Caminho de destino (deve começar com /) |
type | number | Não | Status HTTP: 301, 302, 307 ou 308 |
enabled | boolean | Não | Se o redirecionamento está ativo (padrão: true) |
groupName | string | Não | Rótulo de agrupamento opcional para filtragem/pesquisa no admin |
Áreas de Widgets
Regiões de conteúdo configuráveis:
{
"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!" }]
}
]
}
]
}
]
}
Tipos de Widget
| Tipo | Descrição | Campos Obrigatórios |
|---|---|---|
content | Conteúdo rich text | content (Portable Text) |
menu | Renderiza um menu | menuName |
component | Componente registado | componentId |
Componentes Integrados
| ID do Componente | Descrição |
|---|---|
core:recent-posts | Lista de posts recentes |
core:categories | Lista de categorias |
core:tags | Nuvem de tags |
core:search | Formulário de pesquisa |
core:archives | Arquivos mensais |
Secções
Blocos de conteúdo reutilizáveis que os editores podem inserir em campos Portable Text através do comando 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." }
]
}
]
}
]
}
Propriedades da Secção
| Propriedade | Tipo | Obrigatório | Descrição |
|---|---|---|---|
slug | string | Sim | Identificador seguro para URL |
title | string | Sim | Nome de exibição mostrado no seletor de secções |
description | string | Não | Explica quando usar esta secção |
keywords | array | Não | Termos de pesquisa para encontrar a secção |
content | array | Sim | Blocos Portable Text |
source | string | Não | "theme" (padrão para seeds) ou "import" |
As secções dos ficheiros seed são marcadas como source: "theme" e não podem ser eliminadas do painel de administração. Os editores podem criar as suas próprias secções (source: "user") e inserir qualquer tipo de secção ao editar conteúdo.
Conteúdo
Conteúdo de exemplo organizado por coleção:
{
"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." }]
}
]
}
}
]
}
}
Propriedades da Entrada de Conteúdo
| Propriedade | Tipo | Obrigatório | Descrição |
|---|---|---|---|
id | string | Sim | ID local do seed para referências |
slug | string | Sim | Slug de URL |
status | string | Não | "published" ou "draft" (padrão: "published") |
data | object | Sim | Valores dos campos |
bylines | array | Não | Créditos de autoria ordenados (byline, roleLabel opcional) |
taxonomies | object | Não | Atribuições de termos por nome de taxonomia |
Referências de Conteúdo
Referencie outras entradas de conteúdo usando o prefixo $ref::
{
"data": {
"related_posts": ["$ref:another-post", "$ref:third-post"]
}
}
O prefixo $ref: resolve IDs do seed para IDs da base de dados durante o seeding.
Referências de Media
Incluir imagens a partir de URLs:
{
"data": {
"featured_image": {
"$media": {
"url": "https://images.unsplash.com/photo-xxx",
"alt": "Description of the image",
"filename": "hero.jpg",
"caption": "Photo by Someone"
}
}
}
}
Incluir imagens locais de .emdash/media/:
{
"data": {
"featured_image": {
"$media": {
"file": "hero.jpg",
"alt": "Description of the image"
}
}
}
}
Propriedades de Media
| Propriedade | Tipo | Obrigatório | Descrição |
|---|---|---|---|
url | string | Sim* | URL remoto para descarregar |
file | string | Sim* | Nome do ficheiro local em .emdash/media/ |
alt | string | Não | Texto alternativo para acessibilidade |
filename | string | Não | Substituir nome do ficheiro |
caption | string | Não | Legenda da media |
*É necessário url ou file, não ambos.
Aplicar Seeds Programaticamente
Use a API de seed para ferramentas CLI ou scripts:
import { applySeed, validateSeed } from "emdash/seed";
import seedData from "./.emdash/seed.json";
// Validar primeiro
const validation = validateSeed(seedData);
if (!validation.valid) {
console.error(validation.errors);
process.exit(1);
}
// Aplicar seed
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 }
// }
Opções de Aplicação
| Opção | Tipo | Padrão | Descrição |
|---|---|---|---|
includeContent | boolean | false | Criar entradas de conteúdo de exemplo |
onConflict | string | "skip" | "skip", "update" ou "error" |
mediaBasePath | string | — | Caminho base para ficheiros de media locais |
storage | Storage | — | Adaptador de armazenamento para uploads de media |
baseUrl | string | — | URL base para URLs de media |
Idempotência
O seeding é seguro para executar múltiplas vezes. Comportamento de conflito por tipo de entidade:
| Entidade | Comportamento |
|---|---|
| Coleção | Ignorar se o slug existir |
| Campo | Ignorar se coleção + slug existir |
| Definição de taxonomia | Ignorar se o nome existir |
| Termo de taxonomia | Ignorar se nome + slug existir |
| Perfil de autoria | Ignorar se o slug existir |
| Menu | Ignorar se o nome existir |
| Itens de menu | Substituir todos (menu é recriado) |
| Redirecionamento | Ignorar se a origem existir |
| Área de widgets | Ignorar se o nome existir |
| Widgets | Substituir todos (área é recriada) |
| Secção | Ignorar se o slug existir |
| Configurações | Atualizar (configurações devem poder mudar) |
| Conteúdo | Ignorar se o slug existir na coleção |
Validação
Os ficheiros seed são validados antes da aplicação:
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));
Verificações de validação:
- Os campos obrigatórios estão presentes
- Os slugs seguem as convenções de nomenclatura (minúsculas, underscores)
- Os tipos de campo são válidos
- As referências apontam para conteúdo existente
- Os pais de termos hierárquicos existem
- Os caminhos de redirecionamento são URLs locais seguros
- As origens de redirecionamento são únicas
- Sem slugs duplicados dentro das coleções
Comandos CLI
# Aplicar ficheiro seed
npx emdash seed .emdash/seed.json
# Aplicar sem conteúdo de exemplo
npx emdash seed .emdash/seed.json --no-content
# Apenas validar
npx emdash seed .emdash/seed.json --validate
# Exportar esquema atual como seed
npx emdash export-seed > seed.json
# Exportar com conteúdo
npx emdash export-seed --with-content > seed.json
Próximos Passos
- Criar Temas — Construir um tema completo
- Visão Geral de Temas — Como os temas funcionam